txMetadata )
{
InternalTransaction tx = super.beginTransaction( type, loginContext, txTimeout, txMetadata );
BoltTransactionListener.onTransactionCreate( tx );
return tx;
}
+ //NOTE
+
+ public TransactionStateMachineV3SPI( GraphDatabaseAPI db, BoltChannel boltChannel, Duration txAwaitDuration, Clock clock )
+ {
+ super( db, boltChannel, txAwaitDuration, clock );
+ }
+
+ @Override
+ protected BoltResultHandle newBoltResultHandle( String statement, MapValue params, TransactionalContext transactionalContext )
+ {
+ return new BoltResultHandleV3( statement, params, transactionalContext );
+ }
+
+ private class BoltResultHandleV3 extends BoltResultHandleV1
+ {
+ BoltResultHandleV3( String statement, MapValue params, TransactionalContext transactionalContext )
+ {
+ super( statement, params, transactionalContext );
+ }
+
+ @Override
+ protected BoltResult newBoltResult( QueryResultProvider result, Clock clock )
+ {
+ return new CypherAdapterStreamV3( result.queryResult(), clock );
+ }
+ }
}
diff --git a/src/blob/java/org/neo4j/cypher/internal/codegen/ParameterConverter.java b/blob-feature/src/main/java/org/neo4j/cypher/internal/codegen/ParameterConverter.java
similarity index 99%
rename from src/blob/java/org/neo4j/cypher/internal/codegen/ParameterConverter.java
rename to blob-feature/src/main/java/org/neo4j/cypher/internal/codegen/ParameterConverter.java
index 975ce108..5b872ae0 100644
--- a/src/blob/java/org/neo4j/cypher/internal/codegen/ParameterConverter.java
+++ b/blob-feature/src/main/java/org/neo4j/cypher/internal/codegen/ParameterConverter.java
@@ -33,7 +33,7 @@
import java.util.Iterator;
import java.util.List;
-import cn.graiph.blob.Blob;
+import cn.pandadb.blob.Blob;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Path;
import org.neo4j.graphdb.PropertyContainer;
diff --git a/src/blob/java/org/neo4j/internal/kernel/api/procs/Neo4jTypes.java b/blob-feature/src/main/java/org/neo4j/internal/kernel/api/procs/Neo4jTypes.java
similarity index 99%
rename from src/blob/java/org/neo4j/internal/kernel/api/procs/Neo4jTypes.java
rename to blob-feature/src/main/java/org/neo4j/internal/kernel/api/procs/Neo4jTypes.java
index 25ace7e0..8d8b7355 100644
--- a/src/blob/java/org/neo4j/internal/kernel/api/procs/Neo4jTypes.java
+++ b/blob-feature/src/main/java/org/neo4j/internal/kernel/api/procs/Neo4jTypes.java
@@ -50,6 +50,7 @@ public class Neo4jTypes
//NOTE: blob
public static final NeoBlobType NTBlob = new NeoBlobType();
+ //NOTE
public static class NeoBlobType extends AnyType
{
diff --git a/src/blob/java/org/neo4j/kernel/api/index/ArrayEncoder.java b/blob-feature/src/main/java/org/neo4j/kernel/api/index/ArrayEncoder.java
similarity index 99%
rename from src/blob/java/org/neo4j/kernel/api/index/ArrayEncoder.java
rename to blob-feature/src/main/java/org/neo4j/kernel/api/index/ArrayEncoder.java
index 20a5789d..cbadf7fe 100644
--- a/src/blob/java/org/neo4j/kernel/api/index/ArrayEncoder.java
+++ b/blob-feature/src/main/java/org/neo4j/kernel/api/index/ArrayEncoder.java
@@ -26,7 +26,7 @@
import java.time.ZonedDateTime;
import java.util.Base64;
-import cn.graiph.blob.Blob;
+import cn.pandadb.blob.Blob;
import org.neo4j.string.UTF8;
import org.neo4j.values.storable.BlobValue;
import org.neo4j.values.storable.CoordinateReferenceSystem;
diff --git a/src/blob/java/org/neo4j/kernel/impl/api/state/AppendOnlyValuesContainer.java b/blob-feature/src/main/java/org/neo4j/kernel/impl/api/state/AppendOnlyValuesContainer.java
similarity index 99%
rename from src/blob/java/org/neo4j/kernel/impl/api/state/AppendOnlyValuesContainer.java
rename to blob-feature/src/main/java/org/neo4j/kernel/impl/api/state/AppendOnlyValuesContainer.java
index 887d9f8b..7f0a76c5 100644
--- a/src/blob/java/org/neo4j/kernel/impl/api/state/AppendOnlyValuesContainer.java
+++ b/blob-feature/src/main/java/org/neo4j/kernel/impl/api/state/AppendOnlyValuesContainer.java
@@ -33,9 +33,9 @@
import java.util.List;
import javax.annotation.Nonnull;
-import cn.graiph.blob.Blob;
-import cn.graiph.blob.BlobIO;
-import cn.graiph.blob.BlobWithId;
+import cn.pandadb.blob.Blob;
+import cn.pandadb.blob.BlobIO;
+import cn.pandadb.blob.BlobWithId;
import org.neo4j.graphdb.Resource;
import org.neo4j.io.ByteUnit;
import org.neo4j.kernel.impl.util.collection.Memory;
diff --git a/src/blob/java/org/neo4j/kernel/impl/index/schema/Types.java b/blob-feature/src/main/java/org/neo4j/kernel/impl/index/schema/Types.java
similarity index 100%
rename from src/blob/java/org/neo4j/kernel/impl/index/schema/Types.java
rename to blob-feature/src/main/java/org/neo4j/kernel/impl/index/schema/Types.java
diff --git a/src/blob/java/org/neo4j/kernel/impl/proc/TypeMappers.java b/blob-feature/src/main/java/org/neo4j/kernel/impl/proc/TypeMappers.java
similarity index 99%
rename from src/blob/java/org/neo4j/kernel/impl/proc/TypeMappers.java
rename to blob-feature/src/main/java/org/neo4j/kernel/impl/proc/TypeMappers.java
index 13377ed6..934954e5 100644
--- a/src/blob/java/org/neo4j/kernel/impl/proc/TypeMappers.java
+++ b/blob-feature/src/main/java/org/neo4j/kernel/impl/proc/TypeMappers.java
@@ -34,7 +34,7 @@
import java.util.function.Function;
import java.util.stream.Collectors;
-import cn.graiph.blob.Blob;
+import cn.pandadb.blob.Blob;
import org.neo4j.helpers.collection.Iterables;
import org.neo4j.internal.kernel.api.exceptions.ProcedureException;
import org.neo4j.internal.kernel.api.procs.DefaultParameterValue;
@@ -132,7 +132,8 @@ public TypeMappers( EmbeddedProxySPI proxySPI )
private void registerScalarsAndCollections()
{
//NOTE: supports blob
- registerType(Blob.class, TO_BLOB);
+ registerType( Blob.class, TO_BLOB );
+ //NOTE
registerType( String.class, TO_STRING );
registerType( long.class, TO_INTEGER );
diff --git a/src/blob/java/org/neo4j/kernel/impl/storageengine/impl/recordstorage/PropertyCreator.java b/blob-feature/src/main/java/org/neo4j/kernel/impl/storageengine/impl/recordstorage/PropertyCreator.java
similarity index 96%
rename from src/blob/java/org/neo4j/kernel/impl/storageengine/impl/recordstorage/PropertyCreator.java
rename to blob-feature/src/main/java/org/neo4j/kernel/impl/storageengine/impl/recordstorage/PropertyCreator.java
index 1139754f..0739fdc0 100644
--- a/src/blob/java/org/neo4j/kernel/impl/storageengine/impl/recordstorage/PropertyCreator.java
+++ b/blob-feature/src/main/java/org/neo4j/kernel/impl/storageengine/impl/recordstorage/PropertyCreator.java
@@ -19,23 +19,18 @@
*/
package org.neo4j.kernel.impl.storageengine.impl.recordstorage;
-import java.util.Iterator;
-import java.util.function.Consumer;
-
-import org.neo4j.kernel.impl.InstanceContext;
import org.neo4j.kernel.impl.store.DynamicRecordAllocator;
import org.neo4j.kernel.impl.store.PropertyStore;
import org.neo4j.kernel.impl.store.PropertyType;
import org.neo4j.kernel.impl.store.id.IdSequence;
-import org.neo4j.kernel.impl.store.record.DynamicRecord;
-import org.neo4j.kernel.impl.store.record.PrimitiveRecord;
-import org.neo4j.kernel.impl.store.record.PropertyBlock;
-import org.neo4j.kernel.impl.store.record.PropertyRecord;
-import org.neo4j.kernel.impl.store.record.Record;
+import org.neo4j.kernel.impl.store.record.*;
import org.neo4j.kernel.impl.transaction.state.RecordAccess;
import org.neo4j.kernel.impl.transaction.state.RecordAccess.RecordProxy;
import org.neo4j.values.storable.Value;
+import java.util.Iterator;
+import java.util.function.Consumer;
+
public class PropertyCreator
{
private final DynamicRecordAllocator stringRecordAllocator;
@@ -174,7 +169,7 @@ private void removeProperty( PrimitiveRecord primitive, PropertyRecord host, Pro
{
host.removePropertyBlock( block.getKeyIndexId() );
//on delete
- block.getType().onPropertyDelete( InstanceContext.of( this.propertyRecordIdGenerator ), primitive, host, block);
+ block.getType().onPropertyDelete(primitive, host, block);
host.setChanged( primitive );
for ( DynamicRecord record : block.getValueRecords() )
diff --git a/src/blob/java/org/neo4j/kernel/impl/storageengine/impl/recordstorage/PropertyDeleter.java b/blob-feature/src/main/java/org/neo4j/kernel/impl/storageengine/impl/recordstorage/PropertyDeleter.java
similarity index 93%
rename from src/blob/java/org/neo4j/kernel/impl/storageengine/impl/recordstorage/PropertyDeleter.java
rename to blob-feature/src/main/java/org/neo4j/kernel/impl/storageengine/impl/recordstorage/PropertyDeleter.java
index 1f0ea234..c3698e3d 100644
--- a/src/blob/java/org/neo4j/kernel/impl/storageengine/impl/recordstorage/PropertyDeleter.java
+++ b/blob-feature/src/main/java/org/neo4j/kernel/impl/storageengine/impl/recordstorage/PropertyDeleter.java
@@ -18,13 +18,7 @@
* along with this program. If not, see .
*/
package org.neo4j.kernel.impl.storageengine.impl.recordstorage;
-
-import org.neo4j.kernel.impl.InstanceContext;
-import org.neo4j.kernel.impl.store.record.DynamicRecord;
-import org.neo4j.kernel.impl.store.record.PrimitiveRecord;
-import org.neo4j.kernel.impl.store.record.PropertyBlock;
-import org.neo4j.kernel.impl.store.record.PropertyRecord;
-import org.neo4j.kernel.impl.store.record.Record;
+import org.neo4j.kernel.impl.store.record.*;
import org.neo4j.kernel.impl.transaction.state.RecordAccess;
import org.neo4j.kernel.impl.transaction.state.RecordAccess.RecordProxy;
@@ -50,7 +44,7 @@ public void deletePropertyChain( PrimitiveRecord primitive,
PropertyRecord propRecord = propertyChange.forChangingData();
propRecord.forEach( block -> {
- block.getType().onPropertyDelete( InstanceContext.of(propertyRecords), primitive, propRecord, block );
+ block.getType().onPropertyDelete( primitive, propRecord, block );
} );
deletePropertyRecordIncludingValueRecords( propRecord );
@@ -138,7 +132,7 @@ private void removeProperty( RecordProxy
pri
+ propertyId + "]" );
}
- block.getType().onPropertyDelete( InstanceContext.of( propertyRecords ), primitive, propRecord, block);
+ block.getType().onPropertyDelete( primitive, propRecord, block);
for ( DynamicRecord valueRecord : block.getValueRecords() )
{
diff --git a/src/blob/java/org/neo4j/kernel/impl/storageengine/impl/recordstorage/RecordPropertyCursor.java b/blob-feature/src/main/java/org/neo4j/kernel/impl/storageengine/impl/recordstorage/RecordPropertyCursor.java
similarity index 98%
rename from src/blob/java/org/neo4j/kernel/impl/storageengine/impl/recordstorage/RecordPropertyCursor.java
rename to blob-feature/src/main/java/org/neo4j/kernel/impl/storageengine/impl/recordstorage/RecordPropertyCursor.java
index fccf4f55..4399aa74 100644
--- a/src/blob/java/org/neo4j/kernel/impl/storageengine/impl/recordstorage/RecordPropertyCursor.java
+++ b/blob-feature/src/main/java/org/neo4j/kernel/impl/storageengine/impl/recordstorage/RecordPropertyCursor.java
@@ -22,7 +22,6 @@
import java.nio.ByteBuffer;
import org.neo4j.io.pagecache.PageCursor;
-import org.neo4j.kernel.impl.InstanceContext;
import org.neo4j.kernel.impl.blob.StoreBlobIO;
import org.neo4j.kernel.impl.store.GeometryType;
import org.neo4j.kernel.impl.store.LongerShortString;
@@ -241,7 +240,7 @@ private Value readValue()
case TEMPORAL:
return temporalValue();
case BLOB:
- return StoreBlobIO.readBlobValue( InstanceContext.of( read ), this.getBlocks());
+ return StoreBlobIO.readBlobValue( this.getBlocks());
default:
throw new IllegalStateException( "Unsupported PropertyType: " + type.name() );
}
@@ -407,6 +406,6 @@ private ArrayValue array( RecordPropertyCursor cursor, long reference, PageCurso
{
ByteBuffer buffer = cursor.buffer = read.loadArray( reference, cursor.buffer, page );
buffer.flip();
- return PropertyStore.readArrayFromBuffer( InstanceContext.of( read ), buffer );
+ return PropertyStore.readArrayFromBuffer( buffer );
}
}
diff --git a/src/blob/java/org/neo4j/kernel/impl/store/DynamicArrayStore.java b/blob-feature/src/main/java/org/neo4j/kernel/impl/store/DynamicArrayStore.java
similarity index 96%
rename from src/blob/java/org/neo4j/kernel/impl/store/DynamicArrayStore.java
rename to blob-feature/src/main/java/org/neo4j/kernel/impl/store/DynamicArrayStore.java
index a13aa6c9..378eb0b5 100644
--- a/src/blob/java/org/neo4j/kernel/impl/store/DynamicArrayStore.java
+++ b/blob-feature/src/main/java/org/neo4j/kernel/impl/store/DynamicArrayStore.java
@@ -30,12 +30,10 @@
import java.time.ZonedDateTime;
import java.util.Collection;
-import cn.graiph.blob.Blob;
-import cn.graiph.util.ContextMap;
+import cn.pandadb.blob.Blob;
import org.neo4j.helpers.collection.Pair;
import org.neo4j.io.pagecache.PageCache;
import org.neo4j.kernel.configuration.Config;
-import org.neo4j.kernel.impl.InstanceContext;
import org.neo4j.kernel.impl.blob.StoreBlobIO;
import org.neo4j.kernel.impl.store.format.Capability;
import org.neo4j.kernel.impl.store.format.RecordFormats;
@@ -207,7 +205,7 @@ private static void allocateFromBlob( Collection target, Blob[] a
for ( int i = 0; i < array.length; i++ )
{
Blob blob = array[i];
- byte[] bytes = StoreBlobIO.saveAndEncodeBlobAsByteArray( InstanceContext.of( recordAllocator ), blob );
+ byte[] bytes = StoreBlobIO.saveAndEncodeBlobAsByteArray( blob );
blobsAsBytes[i] = bytes;
totalBytesRequired += 4/*byte[].length*/ + bytes.length;
}
@@ -346,7 +344,7 @@ else if ( type.equals( DurationValue.class ) )
}
}
- public static Value getRightArray( ContextMap ic, Pair data )
+ public static Value getRightArray( Pair data )
{
byte[] header = data.first();
byte[] bArray = data.other();
@@ -373,18 +371,18 @@ else if ( typeId == PropertyType.BLOB.intValue() )
ByteBuffer headerBuffer = ByteBuffer.wrap( header, 1/*skip the type*/, header.length - 1 );
int arrayLength = headerBuffer.getInt();
ByteBuffer dataBuffer = ByteBuffer.wrap( bArray );
- Blob[] result = StoreBlobIO.readBlobArray( ic, dataBuffer, arrayLength );
+ Blob[] result = StoreBlobIO.readBlobArray( dataBuffer, arrayLength );
return Values.blobArray( result );
}
else if ( typeId == PropertyType.GEOMETRY.intValue() )
{
GeometryType.GeometryHeader geometryHeader = GeometryType.GeometryHeader.fromArrayHeaderBytes(header);
- return GeometryType.decodeGeometryArray( ic, geometryHeader, bArray );
+ return GeometryType.decodeGeometryArray( geometryHeader, bArray );
}
else if ( typeId == PropertyType.TEMPORAL.intValue() )
{
TemporalType.TemporalHeader temporalHeader = TemporalType.TemporalHeader.fromArrayHeaderBytes(header);
- return TemporalType.decodeTemporalArray( ic, temporalHeader, bArray );
+ return TemporalType.decodeTemporalArray( temporalHeader, bArray );
}
else
{
@@ -410,6 +408,6 @@ else if ( typeId == PropertyType.TEMPORAL.intValue() )
public Object getArrayFor( Iterable records )
{
- return getRightArray( InstanceContext.of( this ), readFullByteArray( records, PropertyType.ARRAY ) ).asObject();
+ return getRightArray( readFullByteArray( records, PropertyType.ARRAY ) ).asObject();
}
}
diff --git a/src/blob/java/org/neo4j/kernel/impl/store/PropertyStore.java b/blob-feature/src/main/java/org/neo4j/kernel/impl/store/PropertyStore.java
similarity index 97%
rename from src/blob/java/org/neo4j/kernel/impl/store/PropertyStore.java
rename to blob-feature/src/main/java/org/neo4j/kernel/impl/store/PropertyStore.java
index fb21dd4a..c2180369 100644
--- a/src/blob/java/org/neo4j/kernel/impl/store/PropertyStore.java
+++ b/blob-feature/src/main/java/org/neo4j/kernel/impl/store/PropertyStore.java
@@ -29,14 +29,12 @@
import java.util.List;
import java.util.function.ToIntFunction;
-import cn.graiph.blob.Blob;
-import cn.graiph.util.ContextMap;
+import cn.pandadb.blob.Blob;
import org.neo4j.helpers.collection.Iterables;
import org.neo4j.helpers.collection.Pair;
import org.neo4j.io.pagecache.PageCache;
import org.neo4j.io.pagecache.PageCursor;
import org.neo4j.kernel.configuration.Config;
-import org.neo4j.kernel.impl.InstanceContext;
import org.neo4j.kernel.impl.blob.StoreBlobIO;
import org.neo4j.kernel.impl.store.format.Capability;
import org.neo4j.kernel.impl.store.format.RecordFormats;
@@ -632,7 +630,7 @@ public void writeDateTime( long epochSecondUTC, int nano, String zoneId ) throws
@Override
public void writeBlob( Blob blob )
{
- StoreBlobIO.saveBlob( InstanceContext.of( stringAllocator ), blob, this.keyId, this.block );
+ StoreBlobIO.saveBlob( blob, this.keyId, this.block );
}
}
@@ -678,7 +676,7 @@ Value getArrayFor( PropertyBlock propertyBlock )
private Value getArrayFor( Iterable records )
{
- return getRightArray( InstanceContext.of( this ), arrayStore.readFullByteArray( records, PropertyType.ARRAY ) );
+ return getRightArray( arrayStore.readFullByteArray( records, PropertyType.ARRAY ) );
}
@Override
@@ -720,7 +718,7 @@ public ToIntFunction newValueEncodedSizeCalculator()
return new PropertyValueRecordSizeCalculator( this );
}
- public static ArrayValue readArrayFromBuffer( ContextMap ic, ByteBuffer buffer )
+ public static ArrayValue readArrayFromBuffer( ByteBuffer buffer )
{
if ( buffer.limit() <= 0 )
{
@@ -748,7 +746,7 @@ public static ArrayValue readArrayFromBuffer( ContextMap ic, ByteBuffer buffer )
else if ( typeId == PropertyType.BLOB.intValue() )
{
int arrayLength = buffer.getInt();
- Blob[] result = StoreBlobIO.readBlobArray( ic, buffer, arrayLength );
+ Blob[] result = StoreBlobIO.readBlobArray( buffer, arrayLength );
return Values.blobArray(result);
}
else if ( typeId == PropertyType.GEOMETRY.intValue() )
@@ -756,14 +754,14 @@ else if ( typeId == PropertyType.GEOMETRY.intValue() )
GeometryType.GeometryHeader header = GeometryType.GeometryHeader.fromArrayHeaderByteBuffer( buffer );
byte[] byteArray = new byte[buffer.limit() - buffer.position()];
buffer.get( byteArray );
- return GeometryType.decodeGeometryArray( ic, header, byteArray );
+ return GeometryType.decodeGeometryArray( header, byteArray );
}
else if ( typeId == PropertyType.TEMPORAL.intValue() )
{
TemporalType.TemporalHeader header = TemporalType.TemporalHeader.fromArrayHeaderByteBuffer( buffer );
byte[] byteArray = new byte[buffer.limit() - buffer.position()];
buffer.get( byteArray );
- return TemporalType.decodeTemporalArray( ic, header, byteArray );
+ return TemporalType.decodeTemporalArray( header, byteArray );
}
else
{
diff --git a/src/blob/java/org/neo4j/kernel/impl/store/PropertyType.java b/blob-feature/src/main/java/org/neo4j/kernel/impl/store/PropertyType.java
similarity index 92%
rename from src/blob/java/org/neo4j/kernel/impl/store/PropertyType.java
rename to blob-feature/src/main/java/org/neo4j/kernel/impl/store/PropertyType.java
index 747ab05c..9076591d 100644
--- a/src/blob/java/org/neo4j/kernel/impl/store/PropertyType.java
+++ b/blob-feature/src/main/java/org/neo4j/kernel/impl/store/PropertyType.java
@@ -21,9 +21,6 @@
import java.util.Arrays;
import java.util.List;
-
-import cn.graiph.util.ContextMap;
-import org.neo4j.kernel.impl.InstanceContext;
import org.neo4j.kernel.impl.blob.StoreBlobIO;
import org.neo4j.kernel.impl.store.format.standard.PropertyRecordFormat;
import org.neo4j.kernel.impl.store.record.DynamicRecord;
@@ -184,15 +181,14 @@ private byte[] headOf( byte[] bytes, int length )
}
@Override
- public void onPropertyDelete( ContextMap ic, PrimitiveRecord primitive, PropertyRecord propRecord, PropertyBlock block )
+ public void onPropertyDelete( PrimitiveRecord primitive, PropertyRecord propRecord, PropertyBlock block )
{
List values = block.getValueRecords();
byte itemType = values.get( 0 ).getData( )[0];
if ( itemType == BLOB.byteValue() )
{
- BlobArray value = (BlobArray) DynamicArrayStore.getRightArray(ic,
- AbstractDynamicStore.readFullByteArrayFromHeavyRecords( block.getValueRecords(), PropertyType.ARRAY ) );
- StoreBlobIO.deleteBlobArrayProperty( ic, value );
+ BlobArray value = (BlobArray) DynamicArrayStore.getRightArray( AbstractDynamicStore.readFullByteArrayFromHeavyRecords( block.getValueRecords(), PropertyType.ARRAY ) );
+ StoreBlobIO.deleteBlobArrayProperty( value );
}
}
},
@@ -257,7 +253,7 @@ public int calculateNumberOfBlocksUsed( long firstBlock )
@Override
public Value value( PropertyBlock block, PropertyStore store )
{
- return StoreBlobIO.readBlobValue( InstanceContext.of( store ), block );
+ return StoreBlobIO.readBlobValue( block );
}
@Override
@@ -267,9 +263,9 @@ public int calculateNumberOfBlocksUsed( long firstBlock )
}
@Override
- public void onPropertyDelete( ContextMap ic, PrimitiveRecord primitive, PropertyRecord propRecord, PropertyBlock block )
+ public void onPropertyDelete( PrimitiveRecord primitive, PropertyRecord propRecord, PropertyBlock block )
{
- StoreBlobIO.deleteBlobProperty( ic, primitive, propRecord, block );
+ StoreBlobIO.deleteBlobProperty( primitive, propRecord, block );
}
};
@@ -388,7 +384,7 @@ public byte[] readDynamicRecordHeader( byte[] recordBytes )
throw new UnsupportedOperationException();
}
- public void onPropertyDelete( ContextMap ic, PrimitiveRecord primitive, PropertyRecord propRecord, PropertyBlock block )
+ public void onPropertyDelete( PrimitiveRecord primitive, PropertyRecord propRecord, PropertyBlock block )
{
//do nothing
}
diff --git a/src/blob/java/org/neo4j/kernel/impl/util/BaseToObjectValueWriter.java b/blob-feature/src/main/java/org/neo4j/kernel/impl/util/BaseToObjectValueWriter.java
similarity index 99%
rename from src/blob/java/org/neo4j/kernel/impl/util/BaseToObjectValueWriter.java
rename to blob-feature/src/main/java/org/neo4j/kernel/impl/util/BaseToObjectValueWriter.java
index 76f3bfba..6aeceecd 100644
--- a/src/blob/java/org/neo4j/kernel/impl/util/BaseToObjectValueWriter.java
+++ b/blob-feature/src/main/java/org/neo4j/kernel/impl/util/BaseToObjectValueWriter.java
@@ -33,7 +33,7 @@
import java.util.Iterator;
import java.util.List;
-import cn.graiph.blob.Blob;
+import cn.pandadb.blob.Blob;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Path;
import org.neo4j.graphdb.PropertyContainer;
diff --git a/src/blob/java/org/neo4j/values/storable/BlobArray.java b/blob-feature/src/main/java/org/neo4j/values/storable/BlobArray.java
similarity index 96%
rename from src/blob/java/org/neo4j/values/storable/BlobArray.java
rename to blob-feature/src/main/java/org/neo4j/values/storable/BlobArray.java
index e0898b0f..db4a2aa6 100644
--- a/src/blob/java/org/neo4j/values/storable/BlobArray.java
+++ b/blob-feature/src/main/java/org/neo4j/values/storable/BlobArray.java
@@ -20,7 +20,7 @@
package org.neo4j.values.storable;
import org.neo4j.values.AnyValue;
-import cn.graiph.blob.Blob;
+import cn.pandadb.blob.Blob;
import org.neo4j.values.ValueMapper;
public class BlobArray extends NonPrimitiveArray
@@ -61,7 +61,7 @@ public AnyValue value( int offset )
@Override
public boolean equals( Value other )
{
- return _support._equals( other );
+ return _support.internalEquals( other );
}
@Override
diff --git a/src/blob/java/org/neo4j/values/storable/ValueWriter.java b/blob-feature/src/main/java/org/neo4j/values/storable/ValueWriter.java
similarity index 99%
rename from src/blob/java/org/neo4j/values/storable/ValueWriter.java
rename to blob-feature/src/main/java/org/neo4j/values/storable/ValueWriter.java
index ec54bf1e..acb1566c 100644
--- a/src/blob/java/org/neo4j/values/storable/ValueWriter.java
+++ b/blob-feature/src/main/java/org/neo4j/values/storable/ValueWriter.java
@@ -19,7 +19,7 @@
*/
package org.neo4j.values.storable;
-import cn.graiph.blob.Blob;
+import cn.pandadb.blob.Blob;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
diff --git a/src/blob/java/org/neo4j/values/storable/Values.java b/blob-feature/src/main/java/org/neo4j/values/storable/Values.java
similarity index 99%
rename from src/blob/java/org/neo4j/values/storable/Values.java
rename to blob-feature/src/main/java/org/neo4j/values/storable/Values.java
index 6790ffe4..578383a7 100644
--- a/src/blob/java/org/neo4j/values/storable/Values.java
+++ b/blob-feature/src/main/java/org/neo4j/values/storable/Values.java
@@ -35,7 +35,7 @@
import java.util.List;
import java.util.Objects;
-import cn.graiph.blob.Blob;
+import cn.pandadb.blob.Blob;
import org.neo4j.graphdb.spatial.CRS;
import org.neo4j.graphdb.spatial.Point;
diff --git a/src/blob/java/org/neo4j/values/utils/PrettyPrinter.java b/blob-feature/src/main/java/org/neo4j/values/utils/PrettyPrinter.java
similarity index 99%
rename from src/blob/java/org/neo4j/values/utils/PrettyPrinter.java
rename to blob-feature/src/main/java/org/neo4j/values/utils/PrettyPrinter.java
index 33cb3cc4..7cae775b 100644
--- a/src/blob/java/org/neo4j/values/utils/PrettyPrinter.java
+++ b/blob-feature/src/main/java/org/neo4j/values/utils/PrettyPrinter.java
@@ -28,7 +28,7 @@
import java.util.Arrays;
import java.util.Deque;
-import cn.graiph.blob.Blob;
+import cn.pandadb.blob.Blob;
import org.neo4j.values.AnyValueWriter;
import org.neo4j.values.storable.CoordinateReferenceSystem;
import org.neo4j.values.storable.TextArray;
diff --git a/blob-feature/src/main/scala/cn/pandadb/blob/module.scala b/blob-feature/src/main/scala/cn/pandadb/blob/module.scala
new file mode 100644
index 00000000..3c82d0ad
--- /dev/null
+++ b/blob-feature/src/main/scala/cn/pandadb/blob/module.scala
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2002-2019 "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 cn.pandadb.blob
+
+import java.io.File
+
+import cn.pandadb.context._
+import cn.pandadb.util.ConfigUtils._
+import cn.pandadb.util._
+import org.neo4j.kernel.impl.blob.{BlobStorage, DefaultBlobFunctions}
+import org.neo4j.kernel.impl.proc.Procedures
+
+class BlobStorageModule extends PandaModule {
+ override def init(ctx: PandaModuleContext): Unit = {
+ //GraphDatabaseStartedEvent
+ PandaEventHub.trigger({
+ case GraphDatabaseStartedEvent(proceduresService, storeDir, neo4jConf, databaseInfo) =>
+ registerProcedure(proceduresService, classOf[DefaultBlobFunctions]);
+ })
+
+ val conf = ctx.configuration;
+ val blobStorage = BlobStorage.create(conf);
+ BlobStorageContext.bindBlobStorage(blobStorage);
+ BlobStorageContext.bindBlobStorageDir(conf.getAsFile("blob.storage.file.dir", ctx.storeDir, new File(ctx.storeDir, "/blob")));
+ }
+
+ private def registerProcedure(proceduresService: Procedures, procedures: Class[_]*) {
+ for (procedure <- procedures) {
+ proceduresService.registerProcedure(procedure);
+ proceduresService.registerFunction(procedure);
+ }
+ }
+
+ override def close(ctx: PandaModuleContext): Unit = {
+ BlobStorageContext.blobStorage.close(ctx);
+ }
+
+ override def start(ctx: PandaModuleContext): Unit = {
+ BlobStorageContext.blobStorage.start(ctx);
+ }
+}
+
+object BlobStorageContext extends ContextMap {
+ def blobStorage: BlobStorage = get[BlobStorage]();
+
+ def bindBlobStorage(blobStorage: BlobStorage): Unit = put[BlobStorage](blobStorage)
+
+ def getDefaultBlobValueStorageClass: Option[String] = getOption("default-blob-value-storage-class")
+
+ def bindBlobStorageDir(dir: File): Unit = put("blob.storage.file.dir", dir)
+
+ def blobStorageDir: File = get("blob.storage.file.dir");
+}
\ No newline at end of file
diff --git a/src/graiph-database/scala/cn/graiph/db/cypher.scala b/blob-feature/src/main/scala/cn/pandadb/cypherplus/cypher_expr.scala
similarity index 83%
rename from src/graiph-database/scala/cn/graiph/db/cypher.scala
rename to blob-feature/src/main/scala/cn/pandadb/cypherplus/cypher_expr.scala
index e23bf2b2..5a1df0a8 100644
--- a/src/graiph-database/scala/cn/graiph/db/cypher.scala
+++ b/blob-feature/src/main/scala/cn/pandadb/cypherplus/cypher_expr.scala
@@ -1,8 +1,7 @@
-package cn.graiph.db
+package cn.pandadb.cypherplus
-import cn.graiph.util.{ContextMap, ReflectUtils}
-import cn.graiph.{CustomPropertyProvider, ValueMatcher}
-import ReflectUtils._
+import cn.pandadb.blob.BlobStorageContext
+import org.neo4j.cypher.internal.runtime.interpreted.commands.AstNode
import org.neo4j.cypher.internal.runtime.interpreted.commands.convert.{ExpressionConverters, ExtendedCommandExpr}
import org.neo4j.cypher.internal.runtime.interpreted.commands.expressions.{Expression => CommandExpression}
import org.neo4j.cypher.internal.runtime.interpreted.commands.predicates.Predicate
@@ -13,20 +12,13 @@ import org.neo4j.cypher.internal.runtime.interpreted.{ExecutionContext, UpdateCo
import org.neo4j.cypher.internal.v3_5.ast.semantics._
import org.neo4j.cypher.internal.v3_5.expressions.Expression.SemanticContext
import org.neo4j.cypher.internal.v3_5.expressions._
-import org.neo4j.cypher.internal.v3_5.parser.Expressions
import org.neo4j.cypher.internal.v3_5.util.InputPosition
import org.neo4j.cypher.internal.v3_5.util.attribution.Id
import org.neo4j.cypher.internal.v3_5.util.symbols._
-import org.neo4j.cypher.internal.v3_5.{expressions => ast}
-import org.neo4j.kernel.configuration.Config
import org.neo4j.values.AnyValue
import org.neo4j.values.storable.{Value, _}
import org.neo4j.values.virtual.VirtualValues
-import org.parboiled.scala._
-/**
- * Created by bluejoe on 2019/7/16.
- */
case class AlgoNameWithThresholdExpr(algorithm: Option[String], threshold: Option[Double])(val position: InputPosition)
extends Expression with ExtendedExpr {
@@ -45,7 +37,7 @@ case class AlgoNameWithThresholdExpr(algorithm: Option[String], threshold: Optio
case class CustomPropertyExpr(map: Expression, propertyKey: PropertyKeyName)(val position: InputPosition)
extends LogicalProperty with ExtendedExpr with ExtendedCommandExpr {
- override def asCanonicalStringVal = s"${map.asCanonicalStringVal}.${propertyKey.asCanonicalStringVal}"
+ override def asCanonicalStringVal: String = s"${map.asCanonicalStringVal}.${propertyKey.asCanonicalStringVal}"
override def makeCommand(id: Id, self: ExpressionConverters): CommandExpression =
CustomPropertyCommand(self.toCommandExpression(id, map), PropertyKey(this.propertyKey.name))
@@ -62,7 +54,7 @@ case class SemanticLikeExpr(lhs: Expression, ant: Option[AlgoNameWithThresholdEx
TypeSignature(argumentTypes = Vector(CTAny, CTAny), outputType = CTBoolean)
)
- override def canonicalOperatorSymbol = this.getClass.getSimpleName
+ override def canonicalOperatorSymbol: String = this.getClass.getSimpleName
override def makeCommand(id: Id, self: ExpressionConverters): CommandExpression =
SemanticLikeCommand(self.toCommandExpression(id, this.lhs), this.ant, self.toCommandExpression(id, this.rhs))
@@ -81,7 +73,7 @@ case class SemanticUnlikeExpr(lhs: Expression, ant: Option[AlgoNameWithThreshold
override def makeCommand(id: Id, self: ExpressionConverters): CommandExpression =
SemanticUnlikeCommand(self.toCommandExpression(id, this.lhs), this.ant, self.toCommandExpression(id, this.rhs))
- override def canonicalOperatorSymbol = this.getClass.getSimpleName
+ override def canonicalOperatorSymbol: String = this.getClass.getSimpleName
override def check(ctx: SemanticContext): SemanticCheck =
SemanticExpressionCheck.check(ctx, this.arguments) chain
@@ -97,7 +89,7 @@ case class SemanticCompareExpr(lhs: Expression, ant: Option[AlgoNameWithThreshol
override def makeCommand(id: Id, self: ExpressionConverters): CommandExpression =
SemanticCompareCommand(self.toCommandExpression(id, this.lhs), this.ant, self.toCommandExpression(id, this.rhs))
- override def canonicalOperatorSymbol = this.getClass.getSimpleName
+ override def canonicalOperatorSymbol: String = this.getClass.getSimpleName
override def check(ctx: SemanticContext): SemanticCheck =
SemanticExpressionCheck.check(ctx, this.arguments) chain
@@ -113,7 +105,7 @@ case class SemanticSetCompareExpr(lhs: Expression, ant: Option[AlgoNameWithThres
override def makeCommand(id: Id, self: ExpressionConverters): CommandExpression =
SemanticSetCompareCommand(self.toCommandExpression(id, this.lhs), this.ant, self.toCommandExpression(id, this.rhs))
- override def canonicalOperatorSymbol = this.getClass.getSimpleName
+ override def canonicalOperatorSymbol: String = this.getClass.getSimpleName
override def check(ctx: SemanticContext): SemanticCheck =
SemanticExpressionCheck.check(ctx, this.arguments) chain
@@ -129,7 +121,7 @@ case class SemanticContainExpr(lhs: Expression, ant: Option[AlgoNameWithThreshol
override def makeCommand(id: Id, self: ExpressionConverters): CommandExpression =
SemanticContainCommand(self.toCommandExpression(id, this.lhs), this.ant, self.toCommandExpression(id, this.rhs))
- override def canonicalOperatorSymbol = this.getClass.getSimpleName
+ override def canonicalOperatorSymbol: String = this.getClass.getSimpleName
override def check(ctx: SemanticContext): SemanticCheck =
SemanticExpressionCheck.check(ctx, this.arguments) chain
@@ -145,7 +137,7 @@ case class SemanticInExpr(lhs: Expression, ant: Option[AlgoNameWithThresholdExpr
override def makeCommand(id: Id, self: ExpressionConverters): CommandExpression =
SemanticInCommand(self.toCommandExpression(id, this.lhs), this.ant, self.toCommandExpression(id, this.rhs))
- override def canonicalOperatorSymbol = this.getClass.getSimpleName
+ override def canonicalOperatorSymbol: String = this.getClass.getSimpleName
override def check(ctx: SemanticContext): SemanticCheck =
SemanticExpressionCheck.check(ctx, this.arguments) chain
@@ -161,7 +153,7 @@ case class SemanticContainSetExpr(lhs: Expression, ant: Option[AlgoNameWithThres
override def makeCommand(id: Id, self: ExpressionConverters): CommandExpression =
SemanticContainSetCommand(self.toCommandExpression(id, this.lhs), this.ant, self.toCommandExpression(id, this.rhs))
- override def canonicalOperatorSymbol = this.getClass.getSimpleName
+ override def canonicalOperatorSymbol: String = this.getClass.getSimpleName
override def check(ctx: SemanticContext): SemanticCheck =
SemanticExpressionCheck.check(ctx, this.arguments) chain
@@ -174,7 +166,7 @@ case class SemanticSetInExpr(lhs: Expression, ant: Option[AlgoNameWithThresholdE
TypeSignature(argumentTypes = Vector(CTAny, CTAny), outputType = CTBoolean)
)
- override def canonicalOperatorSymbol = this.getClass.getSimpleName
+ override def canonicalOperatorSymbol: String = this.getClass.getSimpleName
override def makeCommand(id: Id, self: ExpressionConverters): CommandExpression =
SemanticSetInCommand(self.toCommandExpression(id, this.lhs), this.ant, self.toCommandExpression(id, this.rhs))
@@ -187,19 +179,11 @@ case class SemanticSetInExpr(lhs: Expression, ant: Option[AlgoNameWithThresholdE
/////////////commands/////////////
case class QueryStateEx(state: QueryState) {
- def getInstanceContext(): ContextMap = {
- val config = state match {
- case x: UpdateCountingQueryContext => state._get("query.inner.inner.transactionalContext.tc.graph.graph.config")
- case _ => state._get("query.inner.transactionalContext.tc.graph.graph.config")
- }
- config.asInstanceOf[Config].getInstanceContext
- }
-
def getCustomPropertyProvider(): CustomPropertyProvider =
- getInstanceContext().get[CustomPropertyProvider]()
+ CypherPlusContext.customPropertyProvider
def getValueMatcher(): ValueMatcher =
- getInstanceContext().get[ValueMatcher]()
+ CypherPlusContext.valueMatcher
}
case class CustomPropertyCommand(mapExpr: CommandExpression, propertyKey: KeyToken)
@@ -216,15 +200,15 @@ case class CustomPropertyCommand(mapExpr: CommandExpression, propertyKey: KeyTok
pv.map(Values.unsafeOf(_, true)).getOrElse(Values.NO_VALUE)
}
- def rewrite(f: (CommandExpression) => CommandExpression) = f(CustomPropertyCommand(mapExpr.rewrite(f), propertyKey.rewrite(f)))
+ def rewrite(f: (CommandExpression) => CommandExpression): CommandExpression = f(CustomPropertyCommand(mapExpr.rewrite(f), propertyKey.rewrite(f)))
- override def children = Seq(mapExpr, propertyKey)
+ override def children: Seq[AstNode[_]] = Seq(mapExpr, propertyKey)
- def arguments = Seq(mapExpr)
+ def arguments: Seq[CommandExpression] = Seq(mapExpr)
- def symbolTableDependencies = mapExpr.symbolTableDependencies
+ def symbolTableDependencies: Set[String] = mapExpr.symbolTableDependencies
- override def toString = s"$mapExpr.${propertyKey.name}"
+ override def toString: String = s"$mapExpr.${propertyKey.name}"
}
trait SemanticOperatorSupport {
@@ -249,15 +233,15 @@ trait SemanticOperatorSupport {
override def toString: String = lhsExpr.toString() + this.getOperatorString + rhsExpr.toString()
- def containsIsNull = false
+ def containsIsNull: Boolean = false
- def rewrite(f: (CommandExpression) => CommandExpression) = f(rhsExpr.rewrite(f) match {
+ def rewrite(f: (CommandExpression) => CommandExpression): CommandExpression = f(rhsExpr.rewrite(f) match {
case other => rewriteMethod(lhsExpr.rewrite(f), ant, other)
})
- def arguments = Seq(lhsExpr, rhsExpr)
+ def arguments: Seq[CommandExpression] = Seq(lhsExpr, rhsExpr)
- def symbolTableDependencies = lhsExpr.symbolTableDependencies ++ rhsExpr.symbolTableDependencies
+ def symbolTableDependencies: Set[String] = lhsExpr.symbolTableDependencies ++ rhsExpr.symbolTableDependencies
}
case class SemanticLikeCommand(lhsExpr: CommandExpression, ant: Option[AlgoNameWithThresholdExpr], rhsExpr: CommandExpression)
@@ -276,7 +260,8 @@ case class SemanticLikeCommand(lhsExpr: CommandExpression, ant: Option[AlgoNameW
override def getOperatorString: String = "~:"
- override def rewriteMethod = SemanticLikeCommand(_, _, _)(converter)
+ override def rewriteMethod: (CommandExpression, Option[AlgoNameWithThresholdExpr], CommandExpression) => CommandExpression =
+ SemanticLikeCommand(_, _, _)(converter)
}
@@ -290,7 +275,8 @@ case class SemanticUnlikeCommand(lhsExpr: CommandExpression, ant: Option[AlgoNam
override def getOperatorString: String = "!:"
- override def rewriteMethod = SemanticUnlikeCommand(_, _, _)(converter)
+ override def rewriteMethod: (CommandExpression, Option[AlgoNameWithThresholdExpr], CommandExpression) => CommandExpression =
+ SemanticUnlikeCommand(_, _, _)(converter)
}
case class SemanticContainCommand(lhsExpr: CommandExpression, ant: Option[AlgoNameWithThresholdExpr], rhsExpr: CommandExpression)
@@ -310,7 +296,8 @@ case class SemanticContainCommand(lhsExpr: CommandExpression, ant: Option[AlgoNa
override def getOperatorString: String = ">:"
- override def rewriteMethod = SemanticContainCommand(_, _, _)(converter)
+ override def rewriteMethod: (CommandExpression, Option[AlgoNameWithThresholdExpr], CommandExpression) => CommandExpression =
+ SemanticContainCommand(_, _, _)(converter)
}
case class SemanticInCommand(lhsExpr: CommandExpression, ant: Option[AlgoNameWithThresholdExpr], rhsExpr: CommandExpression)
@@ -322,7 +309,8 @@ case class SemanticInCommand(lhsExpr: CommandExpression, ant: Option[AlgoNameWit
override def getOperatorString: String = "<:"
- override def rewriteMethod = SemanticInCommand(_, _, _)(converter)
+ override def rewriteMethod: (CommandExpression, Option[AlgoNameWithThresholdExpr], CommandExpression) => CommandExpression =
+ SemanticInCommand(_, _, _)(converter)
}
case class SemanticContainSetCommand(lhsExpr: CommandExpression, ant: Option[AlgoNameWithThresholdExpr], rhsExpr: CommandExpression)
@@ -342,7 +330,8 @@ case class SemanticContainSetCommand(lhsExpr: CommandExpression, ant: Option[Alg
override def getOperatorString: String = ">>:"
- override def rewriteMethod = SemanticContainSetCommand(_, _, _)(converter)
+ override def rewriteMethod: (CommandExpression, Option[AlgoNameWithThresholdExpr], CommandExpression) => CommandExpression =
+ SemanticContainSetCommand(_, _, _)(converter)
}
case class SemanticSetInCommand(lhsExpr: CommandExpression, ant: Option[AlgoNameWithThresholdExpr], rhsExpr: CommandExpression)
@@ -354,7 +343,8 @@ case class SemanticSetInCommand(lhsExpr: CommandExpression, ant: Option[AlgoName
override def getOperatorString: String = "<<:"
- override def rewriteMethod = SemanticSetInCommand(_, _, _)(converter)
+ override def rewriteMethod: (CommandExpression, Option[AlgoNameWithThresholdExpr], CommandExpression) => CommandExpression =
+ SemanticSetInCommand(_, _, _)(converter)
}
case class SemanticCompareCommand(lhsExpr: CommandExpression, ant: Option[AlgoNameWithThresholdExpr], rhsExpr: CommandExpression)
@@ -373,7 +363,8 @@ case class SemanticCompareCommand(lhsExpr: CommandExpression, ant: Option[AlgoNa
override def getOperatorString: String = "::"
- override def rewriteMethod = SemanticCompareCommand(_, _, _)(converter)
+ override def rewriteMethod: (CommandExpression, Option[AlgoNameWithThresholdExpr], CommandExpression) => CommandExpression =
+ SemanticCompareCommand(_, _, _)(converter)
}
case class SemanticSetCompareCommand(lhsExpr: CommandExpression, ant: Option[AlgoNameWithThresholdExpr], rhsExpr: CommandExpression)
@@ -396,9 +387,6 @@ case class SemanticSetCompareCommand(lhsExpr: CommandExpression, ant: Option[Alg
override def getOperatorString: String = ":::"
- override def rewriteMethod = SemanticSetCompareCommand(_, _, _)(converter)
-}
-
-class InvalidSemanticOperatorException(compared: AnyValue) extends RuntimeException {
-
+ override def rewriteMethod: (CommandExpression, Option[AlgoNameWithThresholdExpr], CommandExpression) => CommandExpression =
+ SemanticSetCompareCommand(_, _, _)(converter)
}
\ No newline at end of file
diff --git a/blob-feature/src/main/scala/cn/pandadb/cypherplus/module.scala b/blob-feature/src/main/scala/cn/pandadb/cypherplus/module.scala
new file mode 100644
index 00000000..33d38542
--- /dev/null
+++ b/blob-feature/src/main/scala/cn/pandadb/cypherplus/module.scala
@@ -0,0 +1,61 @@
+package cn.pandadb.cypherplus
+
+import java.io.File
+
+import cn.pandadb.blob.CypherPluginRegistry
+import cn.pandadb.util._
+import org.springframework.context.support.FileSystemXmlApplicationContext
+
+class CypherPlusModule extends PandaModule with Logging {
+ override def init(ctx: PandaModuleContext): Unit = {
+ val conf = ctx.configuration;
+ val cypherPluginRegistry = conf.getRaw("blob.plugins.conf").map(x => {
+ val xml = new File(x);
+
+ val path =
+ if (xml.isAbsolute) {
+ xml.getPath
+ }
+ else {
+ val configFilePath = conf.getRaw("config.file.path")
+ if (configFilePath.isDefined) {
+ new File(new File(configFilePath.get).getParentFile, x).getAbsoluteFile.getCanonicalPath
+ }
+ else {
+ xml.getAbsoluteFile.getCanonicalPath
+ }
+ }
+
+ logger.info(s"loading semantic plugins: $path");
+ val appctx = new FileSystemXmlApplicationContext("file:" + path);
+ appctx.getBean[CypherPluginRegistry](classOf[CypherPluginRegistry]);
+ }).getOrElse {
+ logger.info(s"semantic plugins not loaded: blob.plugins.conf=null");
+ new CypherPluginRegistry()
+ }
+
+ val customPropertyProvider = cypherPluginRegistry.createCustomPropertyProvider(conf);
+ val valueMatcher = cypherPluginRegistry.createValueComparatorRegistry(conf);
+
+ CypherPlusContext.bindCustomPropertyProvider(customPropertyProvider);
+ CypherPlusContext.bindValueMatcher(valueMatcher);
+ }
+
+ override def close(ctx: PandaModuleContext): Unit = {
+
+ }
+
+ override def start(ctx: PandaModuleContext): Unit = {
+
+ }
+}
+
+object CypherPlusContext extends ContextMap {
+ def customPropertyProvider: CustomPropertyProvider = get[CustomPropertyProvider]();
+
+ def bindCustomPropertyProvider(customPropertyProvider: CustomPropertyProvider): Unit = put[CustomPropertyProvider](customPropertyProvider);
+
+ def valueMatcher: ValueMatcher = get[ValueMatcher]();
+
+ def bindValueMatcher(valueMatcher: ValueMatcher): Unit = put[ValueMatcher](valueMatcher);
+}
\ No newline at end of file
diff --git a/src/blob/scala/org/neo4j/bolt/blob/messages.scala b/blob-feature/src/main/scala/org/neo4j/bolt/blob/messages.scala
similarity index 91%
rename from src/blob/scala/org/neo4j/bolt/blob/messages.scala
rename to blob-feature/src/main/scala/org/neo4j/bolt/blob/messages.scala
index 986551f3..fed50bb6 100644
--- a/src/blob/scala/org/neo4j/bolt/blob/messages.scala
+++ b/blob-feature/src/main/scala/org/neo4j/bolt/blob/messages.scala
@@ -21,7 +21,8 @@ package org.neo4j.bolt.blob
import java.io.{ByteArrayInputStream, InputStream}
-import cn.graiph.blob.{MimeType, BlobMessageSignature, Blob, InputStreamSource}
+import cn.pandadb.blob.{MimeType, BlobMessageSignature, Blob, InputStreamSource}
+import cn.pandadb.util.PandaException
import org.neo4j.bolt.messaging.Neo4jPack.Unpacker
import org.neo4j.bolt.messaging.{RequestMessage, RequestMessageDecoder}
import org.neo4j.bolt.runtime.BoltResult.Visitor
@@ -41,10 +42,10 @@ trait RequestMessageHandler {
class GetBlobMessage(val blobId: String) extends RequestMessage with RequestMessageHandler {
override def safeToProcessInAnyState(): Boolean = false;
- override def toString = s"GET_BLOB(id=$blobId)";
+ override def toString: String = s"GET_BLOB(id=$blobId)";
@throws[Exception]
- override def accepts(context: StateMachineContext) = {
+ override def accepts(context: StateMachineContext): Unit = {
val opt: Option[Blob] = TransactionalBlobCache.get(blobId)
if (opt.isDefined) {
context.connectionState.onRecords(new BoltResult() {
@@ -86,7 +87,6 @@ class GetBlobMessage(val blobId: String) extends RequestMessage with RequestMess
override def close(): Unit = {
//TODO
- println("server side: CLOSE!!!!!!!!");
}
}, true);
}
@@ -107,7 +107,7 @@ class GetBlobMessageDecoder(val responseHandler: BoltResponseHandler) extends Re
}
class InvalidBlobHandleException(blobId: String)
- extends RuntimeException(s"invalid blob handle: $blobId, make sure it is within an active transaction") {
+ extends PandaException(s"invalid blob handle: $blobId, make sure it is within an active transaction") {
}
diff --git a/src/blob/scala/org/neo4j/bolt/blob/utils.scala b/blob-feature/src/main/scala/org/neo4j/bolt/blob/utils.scala
similarity index 91%
rename from src/blob/scala/org/neo4j/bolt/blob/utils.scala
rename to blob-feature/src/main/scala/org/neo4j/bolt/blob/utils.scala
index f67adbb8..9a61ca12 100644
--- a/src/blob/scala/org/neo4j/bolt/blob/utils.scala
+++ b/blob-feature/src/main/scala/org/neo4j/bolt/blob/utils.scala
@@ -21,8 +21,8 @@ package org.neo4j.bolt.blob
import java.util.concurrent.atomic.AtomicInteger
-import cn.graiph.blob.{BlobIO, BlobId, Blob, BlobEntry}
-import cn.graiph.util.{StreamUtils, ReflectUtils}
+import cn.pandadb.blob.{BlobIO, BlobId, Blob, BlobEntry}
+import cn.pandadb.util.{StreamUtils, ReflectUtils}
import org.apache.commons.codec.digest.DigestUtils
import ReflectUtils._
import org.neo4j.kernel.api.KernelTransaction
@@ -103,9 +103,9 @@ object BoltTransactionListener {
private def transactionId(kt: TopLevelTransaction) = "" + kt.hashCode();
- def currentTopLevelTransaction() = _local.get();
+ def currentTopLevelTransaction(): TopLevelTransaction = _local.get();
- def currentTopLevelTransactionId() = transactionId(_local.get());
+ def currentTopLevelTransactionId(): String = transactionId(_local.get());
def onTransactionCreate(tx: InternalTransaction): Unit = {
tx match {
@@ -120,9 +120,7 @@ object BoltTransactionListener {
}
})
- case _ => {
-
- }
+ case _ =>
}
}
}
@@ -161,17 +159,11 @@ object TransactionalBlobCache {
bid;
};
- def dump(): Unit = {
- println("<<<<<<<<<<<<<<<<<")
- _blobCache.foreach(println);
- println(">>>>>>>>>>>>>>>>>")
- }
-
def get(bid: String): Option[Blob] = {
_blobCache.get(bid).map(_.blob)
};
- def invalidate(transactionId: String) = {
+ def invalidate(transactionId: String): Unit = {
_blobCache --= _blobCache.filter(_._2.transactionId.equals(transactionId)).map(_._1);
};
}
\ No newline at end of file
diff --git a/src/blob/scala/org/neo4j/cypher/internal/runtime/interpreted/CastSupport.scala b/blob-feature/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/CastSupport.scala
similarity index 99%
rename from src/blob/scala/org/neo4j/cypher/internal/runtime/interpreted/CastSupport.scala
rename to blob-feature/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/CastSupport.scala
index 25a98118..5a480742 100644
--- a/src/blob/scala/org/neo4j/cypher/internal/runtime/interpreted/CastSupport.scala
+++ b/blob-feature/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/CastSupport.scala
@@ -21,7 +21,7 @@ package org.neo4j.cypher.internal.runtime.interpreted
import java.time._
import java.time.temporal.TemporalAmount
-import cn.graiph.blob.Blob
+import cn.pandadb.blob.Blob
import org.neo4j.cypher.internal.v3_5.util.CypherTypeException
import org.neo4j.graphdb.spatial.Point
import org.neo4j.values.storable.{ArrayValue, _}
diff --git a/src/blob/scala/org/neo4j/cypher/internal/runtime/interpreted/TransactionBoundPlanContext.scala b/blob-feature/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/TransactionBoundPlanContext.scala
similarity index 97%
rename from src/blob/scala/org/neo4j/cypher/internal/runtime/interpreted/TransactionBoundPlanContext.scala
rename to blob-feature/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/TransactionBoundPlanContext.scala
index b381ce27..e05b3a3e 100644
--- a/src/blob/scala/org/neo4j/cypher/internal/runtime/interpreted/TransactionBoundPlanContext.scala
+++ b/blob-feature/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/TransactionBoundPlanContext.scala
@@ -40,7 +40,7 @@ import org.neo4j.cypher.internal.v3_5.util.{CypherExecutionException, LabelId, P
import scala.collection.JavaConverters._
object TransactionBoundPlanContext {
- def apply(tc: TransactionalContextWrapper, logger: InternalNotificationLogger) =
+ def apply(tc: TransactionalContextWrapper, logger: InternalNotificationLogger): TransactionBoundPlanContext =
new TransactionBoundPlanContext(tc, logger, InstrumentedGraphStatistics(TransactionBoundGraphStatistics(tc.dataRead,
tc.schemaRead),
new MutableGraphStatisticsSnapshot()))
@@ -66,7 +66,7 @@ class TransactionBoundPlanContext(tc: TransactionalContextWrapper, logger: Inter
override def indexGetForLabelAndProperties(labelName: String, propertyKeys: Seq[String]): Option[IndexDescriptor] = evalOrNone {
try {
val descriptor = toLabelSchemaDescriptor(this, labelName, propertyKeys)
- getOnlineIndex(tc.schemaRead.index(descriptor.getLabelId, descriptor.getPropertyIds:_*))
+ getOnlineIndex(tc.schemaRead.index(descriptor.getLabelId, descriptor.getPropertyIds: _*))
} catch {
case _: KernelException => None
}
@@ -132,7 +132,8 @@ class TransactionBoundPlanContext(tc: TransactionalContextWrapper, logger: Inter
case _: types.GeometryType | _: types.PointType =>
ValueCategory.GEOMETRY
- case _: types.DateTimeType | _: types.LocalDateTimeType | _: types.DateType | _: types.TimeType | _: types.LocalTimeType | _: types.DurationType =>
+ case _: types.DateTimeType | _: types.LocalDateTimeType | _: types.DateType | _: types.TimeType |
+ _: types.LocalTimeType | _: types.DurationType =>
ValueCategory.TEMPORAL
// For everything else, we don't know
@@ -167,7 +168,7 @@ class TransactionBoundPlanContext(tc: TransactionalContextWrapper, logger: Inter
override val txIdProvider = LastCommittedTxIdProvider(tc.graph)
- override def procedureSignature(name: QualifiedName) = {
+ override def procedureSignature(name: QualifiedName): ProcedureSignature = {
val kn = new procs.QualifiedName(name.namespace.asJava, name.name)
val procedures = tc.kernelTransaction.procedures()
val handle = procedures.procedureGet(kn)
diff --git a/src/blob/scala/org/neo4j/cypher/internal/runtime/interpreted/commands/coerce.scala b/blob-feature/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/commands/coerce.scala
similarity index 100%
rename from src/blob/scala/org/neo4j/cypher/internal/runtime/interpreted/commands/coerce.scala
rename to blob-feature/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/commands/coerce.scala
diff --git a/src/blob/scala/org/neo4j/cypher/internal/runtime/interpreted/commands/convert/CommunityExpressionConverter.scala b/blob-feature/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/commands/convert/CommunityExpressionConverter.scala
similarity index 95%
rename from src/blob/scala/org/neo4j/cypher/internal/runtime/interpreted/commands/convert/CommunityExpressionConverter.scala
rename to blob-feature/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/commands/convert/CommunityExpressionConverter.scala
index 35ea318c..ef0ff944 100644
--- a/src/blob/scala/org/neo4j/cypher/internal/runtime/interpreted/commands/convert/CommunityExpressionConverter.scala
+++ b/blob-feature/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/commands/convert/CommunityExpressionConverter.scala
@@ -50,7 +50,7 @@ case class CommunityExpressionConverter(tokenContext: TokenContext) extends Expr
override def toCommandProjection(id: Id, projections: Map[String, Expression],
self: ExpressionConverters): Option[CommandProjection] = {
- val projected = for ((k,Some(v)) <- projections.mapValues(e => toCommandExpression(id, e, self))) yield (k,v)
+ val projected = for ((k, Some(v)) <- projections.mapValues(e => toCommandExpression(id, e, self ))) yield (k, v)
if (projected.size < projections.size) None else Some(InterpretedCommandProjection(projected))
}
@@ -68,8 +68,8 @@ case class CommunityExpressionConverter(tokenContext: TokenContext) extends Expr
case e: ast.Or => predicates.Or(self.toCommandPredicate(id, e.lhs), self.toCommandPredicate(id, e.rhs))
case e: ast.Xor => predicates.Xor(self.toCommandPredicate(id, e.lhs), self.toCommandPredicate(id, e.rhs))
case e: ast.And => predicates.And(self.toCommandPredicate(id, e.lhs), self.toCommandPredicate(id, e.rhs))
- case e: ast.Ands => predicates.Ands(NonEmptyList.from(e.exprs.map(self.toCommandPredicate(id,_))))
- case e: ast.Ors => predicates.Ors(NonEmptyList.from(e.exprs.map(self.toCommandPredicate(id,_))))
+ case e: ast.Ands => predicates.Ands(NonEmptyList.from(e.exprs.map(self.toCommandPredicate(id, _))))
+ case e: ast.Ors => predicates.Ors(NonEmptyList.from(e.exprs.map(self.toCommandPredicate(id, _))))
case e: ast.Not => predicates.Not(self.toCommandPredicate(id, e.rhs))
case e: ast.Equals => predicates.Equals(self.toCommandExpression(id, e.lhs), self.toCommandExpression(id, e.rhs))
case e: ast.NotEquals => predicates
@@ -124,17 +124,17 @@ case class CommunityExpressionConverter(tokenContext: TokenContext) extends Expr
self.toCommandExpression(id, e.scope.extractExpression.get))
case e: ast.ListComprehension => listComprehension(id, e, self)
case e: ast.AllIterablePredicate => commands.AllInList(self.toCommandExpression(id, e.expression), e.variable.name,
- e.innerPredicate.map(self.toCommandPredicate(id,_))
+ e.innerPredicate.map(self.toCommandPredicate(id, _))
.getOrElse(predicates.True()))
case e: ast.AnyIterablePredicate => commands.AnyInList(self.toCommandExpression(id, e.expression), e.variable.name,
- e.innerPredicate.map(self.toCommandPredicate(id,_))
+ e.innerPredicate.map(self.toCommandPredicate(id, _))
.getOrElse(predicates.True()))
case e: ast.NoneIterablePredicate => commands.NoneInList(self.toCommandExpression(id, e.expression), e.variable.name,
- e.innerPredicate.map(self.toCommandPredicate(id,_))
+ e.innerPredicate.map(self.toCommandPredicate(id, _))
.getOrElse(predicates.True()))
case e: ast.SingleIterablePredicate => commands
.SingleInList(self.toCommandExpression(id, e.expression), e.variable.name,
- e.innerPredicate.map(self.toCommandPredicate(id,_)).getOrElse(predicates.True()))
+ e.innerPredicate.map(self.toCommandPredicate(id, _)).getOrElse(predicates.True()))
case e: ast.ReduceExpression => commandexpressions
.ReduceFunction(self.toCommandExpression(id, e.list), e.variable.name, self.toCommandExpression(id, e.expression),
e.accumulator.name, self.toCommandExpression(id, e.init))
@@ -143,9 +143,9 @@ case class CommunityExpressionConverter(tokenContext: TokenContext) extends Expr
.NestedPipeExpression(e.pipe, self.toCommandExpression(id, e.projection))
case e: ast.GetDegree => getDegree(id, e, self)
case e: PrefixSeekRangeWrapper => commandexpressions
- .PrefixSeekRangeExpression(e.range.map(self.toCommandExpression(id,_)))
- case e: InequalitySeekRangeWrapper => InequalitySeekRangeExpression(e.range.mapBounds(self.toCommandExpression(id,_)))
- case e: PointDistanceSeekRangeWrapper => PointDistanceSeekRangeExpression(e.range.map(self.toCommandExpression(id,_)))
+ .PrefixSeekRangeExpression(e.range.map(self.toCommandExpression(id, _)))
+ case e: InequalitySeekRangeWrapper => InequalitySeekRangeExpression(e.range.mapBounds(self.toCommandExpression(id, _)))
+ case e: PointDistanceSeekRangeWrapper => PointDistanceSeekRangeExpression(e.range.map(self.toCommandExpression(id, _)))
case e: ast.AndedPropertyInequalities => predicates
.AndedPropertyComparablePredicates(variable(e.variable), toCommandProperty(id, e.property, self),
e.inequalities.map(e => inequalityExpression(id, e, self)))
@@ -154,7 +154,7 @@ case class CommunityExpressionConverter(tokenContext: TokenContext) extends Expr
case e: ResolvedFunctionInvocation =>
val callArgumentCommands = e.callArguments.map(Some(_))
.zipAll(e.fcnSignature.get.inputSignature.map(_.default.map(_.value)), None, None).map {
- case (given, default) => given.map(self.toCommandExpression(id,_))
+ case (given, default) => given.map(self.toCommandExpression(id, _))
.getOrElse(commandexpressions.Literal(default.get))
}
val signature = e.fcnSignature.get
@@ -187,28 +187,34 @@ case class CommunityExpressionConverter(tokenContext: TokenContext) extends Expr
case Avg =>
val inner = self.toCommandExpression(id, invocation.arguments.head)
val command = commandexpressions.Avg(inner)
- if (invocation.distinct)
+ if (invocation.distinct) {
commandexpressions.Distinct(command, inner)
- else
+ }
+ else {
command
+ }
case Ceil => commandexpressions.CeilFunction(self.toCommandExpression(id, invocation.arguments.head))
case Coalesce => commandexpressions.CoalesceFunction(toCommandExpression(id, invocation.arguments, self): _*)
case Collect =>
val inner = self.toCommandExpression(id, invocation.arguments.head)
val command = commandexpressions.Collect(inner)
- if (invocation.distinct)
+ if (invocation.distinct) {
commandexpressions.Distinct(command, inner)
- else
+ }
+ else {
command
+ }
case Cos => commandexpressions.CosFunction(self.toCommandExpression(id, invocation.arguments.head))
case Cot => commandexpressions.CotFunction(self.toCommandExpression(id, invocation.arguments.head))
case Count =>
val inner = self.toCommandExpression(id, invocation.arguments.head)
val command = commandexpressions.Count(inner)
- if (invocation.distinct)
+ if (invocation.distinct) {
commandexpressions.Distinct(command, inner)
- else
+ }
+ else {
command
+ }
case Degrees => commandexpressions.DegreesFunction(self.toCommandExpression(id, invocation.arguments.head))
case E => commandexpressions.EFunction()
case EndNode => commandexpressions
@@ -257,36 +263,44 @@ case class CommunityExpressionConverter(tokenContext: TokenContext) extends Expr
case Max =>
val inner = self.toCommandExpression(id, invocation.arguments.head)
val command = commandexpressions.Max(inner)
- if (invocation.distinct)
+ if (invocation.distinct) {
commandexpressions.Distinct(command, inner)
- else
+ }
+ else {
command
+ }
case Min =>
val inner = self.toCommandExpression(id, invocation.arguments.head)
val command = commandexpressions.Min(inner)
- if (invocation.distinct)
+ if (invocation.distinct) {
commandexpressions.Distinct(command, inner)
- else
+ }
+ else {
command
+ }
case Nodes => commandexpressions.NodesFunction(self.toCommandExpression(id, invocation.arguments.head))
case PercentileCont =>
val firstArg = self.toCommandExpression(id, invocation.arguments.head)
val secondArg = self.toCommandExpression(id, invocation.arguments(1))
val command = commandexpressions.PercentileCont(firstArg, secondArg)
- if (invocation.distinct)
+ if (invocation.distinct) {
commandexpressions.Distinct(command, firstArg)
- else
+ }
+ else {
command
+ }
case PercentileDisc =>
val firstArg = self.toCommandExpression(id, invocation.arguments.head)
val secondArg = self.toCommandExpression(id, invocation.arguments(1))
val command = commandexpressions.PercentileDisc(firstArg, secondArg)
- if (invocation.distinct)
+ if (invocation.distinct) {
commandexpressions.Distinct(command, firstArg)
- else
+ }
+ else {
command
+ }
case Pi => commandexpressions.PiFunction()
case Distance =>
val firstArg = self.toCommandExpression(id, invocation.arguments.head)
@@ -330,17 +344,21 @@ case class CommunityExpressionConverter(tokenContext: TokenContext) extends Expr
case StdDev =>
val inner = self.toCommandExpression(id, invocation.arguments.head)
val command = commandexpressions.Stdev(inner)
- if (invocation.distinct)
+ if (invocation.distinct) {
commandexpressions.Distinct(command, inner)
- else
+ }
+ else {
command
+ }
case StdDevP =>
val inner = self.toCommandExpression(id, invocation.arguments.head)
val command = commandexpressions.StdevP(inner)
- if (invocation.distinct)
+ if (invocation.distinct) {
commandexpressions.Distinct(command, inner)
- else
+ }
+ else {
command
+ }
case Substring =>
commandexpressions.SubstringFunction(
self.toCommandExpression(id, invocation.arguments.head),
@@ -350,10 +368,12 @@ case class CommunityExpressionConverter(tokenContext: TokenContext) extends Expr
case Sum =>
val inner = self.toCommandExpression(id, invocation.arguments.head)
val command = commandexpressions.Sum(inner)
- if (invocation.distinct)
+ if (invocation.distinct) {
commandexpressions.Distinct(command, inner)
- else
+ }
+ else {
command
+ }
case Tail =>
commandexpressions.ListSlice(
self.toCommandExpression(id, invocation.arguments.head),
@@ -379,11 +399,11 @@ case class CommunityExpressionConverter(tokenContext: TokenContext) extends Expr
private def toCommandExpression(id: Id, expression: Option[ast.Expression],
self: ExpressionConverters): Option[CommandExpression] =
- expression.map(self.toCommandExpression(id,_))
+ expression.map(self.toCommandExpression(id, _))
private def toCommandExpression(id: Id, expressions: Seq[ast.Expression],
self: ExpressionConverters): Seq[CommandExpression] =
- expressions.map(self.toCommandExpression(id,_))
+ expressions.map(self.toCommandExpression(id, _))
private def variable(e: ast.LogicalVariable) = commands.expressions.Variable(e.name)
diff --git a/src/blob/scala/org/neo4j/cypher/internal/runtime/interpreted/commands/expressions/cmd_blob.scala b/blob-feature/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/commands/expressions/cmd_blob.scala
similarity index 100%
rename from src/blob/scala/org/neo4j/cypher/internal/runtime/interpreted/commands/expressions/cmd_blob.scala
rename to blob-feature/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/commands/expressions/cmd_blob.scala
diff --git a/src/blob/scala/org/neo4j/cypher/internal/v3_5/ast/semantics/SemanticExpressionCheck.scala b/blob-feature/src/main/scala/org/neo4j/cypher/internal/v3_5/ast/semantics/SemanticExpressionCheck.scala
similarity index 100%
rename from src/blob/scala/org/neo4j/cypher/internal/v3_5/ast/semantics/SemanticExpressionCheck.scala
rename to blob-feature/src/main/scala/org/neo4j/cypher/internal/v3_5/ast/semantics/SemanticExpressionCheck.scala
diff --git a/src/blob/scala/org/neo4j/cypher/internal/v3_5/expressions/ast_blob.scala b/blob-feature/src/main/scala/org/neo4j/cypher/internal/v3_5/expressions/ast_blob.scala
similarity index 98%
rename from src/blob/scala/org/neo4j/cypher/internal/v3_5/expressions/ast_blob.scala
rename to blob-feature/src/main/scala/org/neo4j/cypher/internal/v3_5/expressions/ast_blob.scala
index abf6ef28..32490e37 100644
--- a/src/blob/scala/org/neo4j/cypher/internal/v3_5/expressions/ast_blob.scala
+++ b/blob-feature/src/main/scala/org/neo4j/cypher/internal/v3_5/expressions/ast_blob.scala
@@ -19,7 +19,7 @@ package org.neo4j.cypher.internal.v3_5.expressions
import java.io.File
-import cn.graiph.blob.Blob
+import cn.pandadb.blob.Blob
import org.apache.commons.codec.binary.Base64
import org.neo4j.cypher.internal.v3_5.util.InputPosition
import org.neo4j.cypher.internal.v3_5.{expressions => ast}
diff --git a/src/blob/scala/org/neo4j/cypher/internal/v3_5/parser/Expressions.scala b/blob-feature/src/main/scala/org/neo4j/cypher/internal/v3_5/parser/Expressions.scala
similarity index 99%
rename from src/blob/scala/org/neo4j/cypher/internal/v3_5/parser/Expressions.scala
rename to blob-feature/src/main/scala/org/neo4j/cypher/internal/v3_5/parser/Expressions.scala
index 39434736..8a06a76b 100644
--- a/src/blob/scala/org/neo4j/cypher/internal/v3_5/parser/Expressions.scala
+++ b/blob-feature/src/main/scala/org/neo4j/cypher/internal/v3_5/parser/Expressions.scala
@@ -16,7 +16,7 @@
*/
package org.neo4j.cypher.internal.v3_5.parser
-import cn.graiph.db._
+import cn.pandadb.cypherplus._
import org.neo4j.cypher.internal.v3_5.expressions.{Variable, _}
import org.neo4j.cypher.internal.v3_5.util.InputPosition
import org.neo4j.cypher.internal.v3_5.expressions
diff --git a/src/blob/scala/org/neo4j/cypher/internal/v3_5/util/symbols/BlobType.scala b/blob-feature/src/main/scala/org/neo4j/cypher/internal/v3_5/util/symbols/BlobType.scala
similarity index 100%
rename from src/blob/scala/org/neo4j/cypher/internal/v3_5/util/symbols/BlobType.scala
rename to blob-feature/src/main/scala/org/neo4j/cypher/internal/v3_5/util/symbols/BlobType.scala
diff --git a/src/blob/scala/org/neo4j/cypher/internal/v3_5/util/symbols/TypeSpec.scala b/blob-feature/src/main/scala/org/neo4j/cypher/internal/v3_5/util/symbols/TypeSpec.scala
similarity index 100%
rename from src/blob/scala/org/neo4j/cypher/internal/v3_5/util/symbols/TypeSpec.scala
rename to blob-feature/src/main/scala/org/neo4j/cypher/internal/v3_5/util/symbols/TypeSpec.scala
diff --git a/src/blob/scala/org/neo4j/cypher/internal/v3_5/util/symbols/package.scala b/blob-feature/src/main/scala/org/neo4j/cypher/internal/v3_5/util/symbols/package.scala
similarity index 100%
rename from src/blob/scala/org/neo4j/cypher/internal/v3_5/util/symbols/package.scala
rename to blob-feature/src/main/scala/org/neo4j/cypher/internal/v3_5/util/symbols/package.scala
diff --git a/src/blob/scala/org/neo4j/kernel/impl/blob/DefaultBlobFunctions.scala b/blob-feature/src/main/scala/org/neo4j/kernel/impl/blob/DefaultBlobFunctions.scala
similarity index 97%
rename from src/blob/scala/org/neo4j/kernel/impl/blob/DefaultBlobFunctions.scala
rename to blob-feature/src/main/scala/org/neo4j/kernel/impl/blob/DefaultBlobFunctions.scala
index ed57a578..b62a17d7 100644
--- a/src/blob/scala/org/neo4j/kernel/impl/blob/DefaultBlobFunctions.scala
+++ b/blob-feature/src/main/scala/org/neo4j/kernel/impl/blob/DefaultBlobFunctions.scala
@@ -21,7 +21,8 @@ package org.neo4j.kernel.impl.blob
import java.io.{File, FileInputStream}
-import cn.graiph.blob.{MimeType, Blob}
+import cn.pandadb.blob.{MimeType, Blob}
+import cn.pandadb.util.PandaException
import org.apache.commons.io.IOUtils
import org.neo4j.procedure.{Description, Name, UserFunction}
@@ -186,6 +187,6 @@ class DefaultBlobFunctions {
}
}
-class CypherFunctionException(msg: String) extends RuntimeException(msg) {
+class CypherFunctionException(msg: String) extends PandaException(msg) {
}
\ No newline at end of file
diff --git a/src/blob/scala/org/neo4j/kernel/impl/blob/StoreBlobIO.scala b/blob-feature/src/main/scala/org/neo4j/kernel/impl/blob/StoreBlobIO.scala
similarity index 58%
rename from src/blob/scala/org/neo4j/kernel/impl/blob/StoreBlobIO.scala
rename to blob-feature/src/main/scala/org/neo4j/kernel/impl/blob/StoreBlobIO.scala
index 370617ad..0611f1b9 100644
--- a/src/blob/scala/org/neo4j/kernel/impl/blob/StoreBlobIO.scala
+++ b/blob-feature/src/main/scala/org/neo4j/kernel/impl/blob/StoreBlobIO.scala
@@ -22,8 +22,8 @@ package org.neo4j.kernel.impl.blob
import java.io.InputStream
import java.nio.ByteBuffer
-import cn.graiph.blob._
-import cn.graiph.util.{StreamUtils, ContextMap, Logging}
+import cn.pandadb.blob._
+import cn.pandadb.util.{PandaException, StreamUtils, ContextMap, Logging}
import org.neo4j.kernel.impl.store.record.{PrimitiveRecord, PropertyBlock, PropertyRecord}
import org.neo4j.values.storable.{BlobArray, BlobValue}
@@ -31,52 +31,51 @@ import org.neo4j.values.storable.{BlobArray, BlobValue}
* Created by bluejoe on 2019/3/29.
*/
object StoreBlobIO extends Logging {
- def saveAndEncodeBlobAsByteArray(ic: ContextMap, blob: Blob): Array[Byte] = {
- val bid = ic.get[BlobStorage].save(blob);
+
+ def saveAndEncodeBlobAsByteArray(blob: Blob): Array[Byte] = {
+ val bid = BlobStorageContext.blobStorage.save(blob);
BlobIO.pack(Blob.makeEntry(bid, blob));
}
- def saveBlob(ic: ContextMap, blob: Blob, keyId: Int, block: PropertyBlock) = {
- val bid = ic.get[BlobStorage].save(blob);
+ def saveBlob(blob: Blob, keyId: Int, block: PropertyBlock) {
+ val bid = BlobStorageContext.blobStorage.save(blob);
block.setValueBlocks(BlobIO._pack(Blob.makeEntry(bid, blob), keyId));
}
- def deleteBlobArrayProperty(ic: ContextMap, blobs: BlobArray): Unit = {
- ic.get[BlobStorage].deleteBatch(
+ def deleteBlobArrayProperty(blobs: BlobArray): Unit = {
+ BlobStorageContext.blobStorage.deleteBatch(
blobs.value().map(_.asInstanceOf[BlobWithId].id));
}
- def deleteBlobProperty(ic: ContextMap, primitive: PrimitiveRecord, propRecord: PropertyRecord, block: PropertyBlock): Unit = {
+ def deleteBlobProperty(primitive: PrimitiveRecord, propRecord: PropertyRecord, block: PropertyBlock): Unit = {
val entry = BlobIO.unpack(block.getValueBlocks);
- ic.get[BlobStorage].delete(entry.id);
+ BlobStorageContext.blobStorage.delete(entry.id);
}
- def readBlob(ic: ContextMap, bytes: Array[Byte]): Blob = {
- readBlobValue(ic, StreamUtils.convertByteArray2LongArray(bytes)).blob;
+ def readBlob(bytes: Array[Byte]): Blob = {
+ readBlobValue(StreamUtils.convertByteArray2LongArray(bytes)).blob;
}
- def readBlobArray(ic: ContextMap, dataBuffer: ByteBuffer, arrayLength: Int): Array[Blob] = {
+ def readBlobArray(dataBuffer: ByteBuffer, arrayLength: Int): Array[Blob] = {
(0 to arrayLength - 1).map { x =>
val byteLength = dataBuffer.getInt();
val blobByteArray = new Array[Byte](byteLength);
dataBuffer.get(blobByteArray);
- StoreBlobIO.readBlob(ic, blobByteArray);
+ StoreBlobIO.readBlob(blobByteArray);
}.toArray
}
-
- def readBlobValue(ic: ContextMap, block: PropertyBlock): BlobValue = {
- readBlobValue(ic, block.getValueBlocks);
+ def readBlobValue(block: PropertyBlock): BlobValue = {
+ readBlobValue(block.getValueBlocks);
}
- def readBlobValue(ic: ContextMap, values: Array[Long]): BlobValue = {
+ def readBlobValue(values: Array[Long]): BlobValue = {
val entry = BlobIO.unpack(values);
- val storage = ic.get[BlobStorage];
val blob = Blob.makeStoredBlob(entry, new InputStreamSource {
override def offerStream[T](consume: (InputStream) => T): T = {
val bid = entry.id;
- storage.load(bid).getOrElse(throw new BlobNotExistException(bid)).offerStream(consume)
+ BlobStorageContext.blobStorage.load(bid).getOrElse(throw new BlobNotExistException(bid)).offerStream(consume)
}
});
@@ -84,6 +83,6 @@ object StoreBlobIO extends Logging {
}
}
-class BlobNotExistException(bid: BlobId) extends RuntimeException {
+class BlobNotExistException(bid: BlobId) extends PandaException(s"blob does not exist: $bid") {
}
\ No newline at end of file
diff --git a/src/blob/scala/org/neo4j/kernel/impl/blob/storage.scala b/blob-feature/src/main/scala/org/neo4j/kernel/impl/blob/storage.scala
similarity index 83%
rename from src/blob/scala/org/neo4j/kernel/impl/blob/storage.scala
rename to blob-feature/src/main/scala/org/neo4j/kernel/impl/blob/storage.scala
index 082f6c67..c8736004 100644
--- a/src/blob/scala/org/neo4j/kernel/impl/blob/storage.scala
+++ b/blob-feature/src/main/scala/org/neo4j/kernel/impl/blob/storage.scala
@@ -22,12 +22,12 @@ package org.neo4j.kernel.impl.blob
import java.io.{File, FileInputStream, FileOutputStream, InputStream}
import java.util.UUID
-import cn.graiph.blob.{MimeType, BlobId, Blob, InputStreamSource}
-import cn.graiph.util._
+import cn.pandadb.blob._
+import cn.pandadb.util.StreamUtils._
+import cn.pandadb.util._
import org.apache.commons.io.filefilter.TrueFileFilter
import org.apache.commons.io.{FileUtils, IOUtils}
-import StreamUtils._
-import ConfigUtils._
+
import scala.collection.JavaConversions._
trait BlobStorage extends BatchBlobValueStorage {
@@ -38,7 +38,7 @@ trait BlobStorage extends BatchBlobValueStorage {
def delete(id: BlobId): Unit;
}
-trait BatchBlobValueStorage extends Closable {
+trait BatchBlobValueStorage extends ClosableModuleComponent {
def saveBatch(blobs: Iterable[Blob]): Iterable[BlobId];
def loadBatch(ids: Iterable[BlobId]): Iterable[Option[Blob]];
@@ -48,14 +48,8 @@ trait BatchBlobValueStorage extends Closable {
def iterator(): Iterator[(BlobId, Blob)];
}
-trait Closable {
- def initialize(storeDir: File, conf: Configuration): Unit;
-
- def disconnect(): Unit;
-}
-
object BlobStorage extends Logging {
- def of(bbvs: BatchBlobValueStorage) = {
+ def of(bbvs: BatchBlobValueStorage): BlobStorage = {
logger.info(s"using batch blob storage: ${bbvs}");
new BlobStorage {
@@ -83,11 +77,11 @@ object BlobStorage extends Logging {
override def loadBatch(ids: Iterable[BlobId]): Iterable[Option[Blob]] = bbvs.loadBatch(ids)
- override def disconnect(): Unit = bbvs.disconnect()
+ override def iterator(): Iterator[(BlobId, Blob)] = bbvs.iterator();
- override def initialize(storeDir: File, conf: Configuration): Unit = bbvs.initialize(storeDir, conf)
+ override def start(ctx: PandaModuleContext): Unit = bbvs.start(ctx)
- override def iterator(): Iterator[(BlobId, Blob)] = bbvs.iterator();
+ override def close(ctx: PandaModuleContext): Unit = bbvs.close(ctx)
};
}
@@ -102,7 +96,7 @@ object BlobStorage extends Logging {
class DefaultLocalFileSystemBlobValueStorage extends BatchBlobValueStorage with Logging {
var _rootDir: File = _;
- override def saveBatch(blobs: Iterable[Blob]) = {
+ override def saveBatch(blobs: Iterable[Blob]): Iterable[BlobId] = {
blobs.map(blob => {
val bid = generateId();
val file = locateFile(bid);
@@ -127,7 +121,7 @@ object BlobStorage extends Logging {
ids.map(id => Some(readFromBlobFile(locateFile(id))._2));
}
- override def deleteBatch(ids: Iterable[BlobId]) = {
+ override def deleteBatch(ids: Iterable[BlobId]) {
ids.foreach { id =>
locateFile(id).delete()
}
@@ -164,14 +158,16 @@ object BlobStorage extends Logging {
(blobId, blob);
}
- override def initialize(storeDir: File, conf: Configuration): Unit = {
- val baseDir: File = storeDir; //new File(conf.getRaw("unsupported.dbms.directories.neo4j_home").get());
- _rootDir = conf.getAsFile("blob.storage.file.dir", baseDir, new File(baseDir, "/blob"));
+ override def start(ctx: PandaModuleContext): Unit = {
+ //val baseDir: File = new File(ctx.storeDir, ctx.neo4jConf.getValue("dbms.active_database").get().toString);
+ //new File(conf.getRaw("unsupported.dbms.directories.neo4j_home").get());
+ _rootDir = BlobStorageContext.blobStorageDir;
_rootDir.mkdirs();
logger.info(s"using storage dir: ${_rootDir.getCanonicalPath}");
}
- override def disconnect(): Unit = {
+ override def close(ctx: PandaModuleContext): Unit = {
+
}
override def iterator(): Iterator[(BlobId, Blob)] = {
@@ -183,7 +179,7 @@ object BlobStorage extends Logging {
def createDefault(): BatchBlobValueStorage = {
//will read "default-blob-value-storage-class" entry first
- GlobalContext.getOption("default-blob-value-storage-class")
+ BlobStorageContext.getDefaultBlobValueStorageClass
.map(Class.forName(_).newInstance().asInstanceOf[BatchBlobValueStorage])
.getOrElse(new DefaultLocalFileSystemBlobValueStorage())
}
diff --git a/src/blob/scala/org/neo4j/values/storable/BlobValue.scala b/blob-feature/src/main/scala/org/neo4j/values/storable/BlobValue.scala
similarity index 93%
rename from src/blob/scala/org/neo4j/values/storable/BlobValue.scala
rename to blob-feature/src/main/scala/org/neo4j/values/storable/BlobValue.scala
index 3a061d57..e0bff42a 100644
--- a/src/blob/scala/org/neo4j/values/storable/BlobValue.scala
+++ b/blob-feature/src/main/scala/org/neo4j/values/storable/BlobValue.scala
@@ -20,13 +20,14 @@
package org.neo4j.values.storable
-import cn.graiph.blob.Blob
+import cn.pandadb.blob.Blob
import org.neo4j.hashing.HashFunction
import org.neo4j.values.{AnyValue, ValueMapper}
/**
* Created by bluejoe on 2018/12/12.
*/
+//noinspection ScalaStyle
case class BlobValue(val blob: Blob) extends ScalarValue {
override def unsafeCompareTo(value: Value): Int = blob.length.compareTo(value.asInstanceOf[BlobValue].blob.length)
@@ -71,9 +72,9 @@ class BlobArraySupport[X <: BlobArraySupport[X]](val blobs: Array[Blob]) {
writer.endArray()
}
- def unsafeCompareTo(other: Value): Int = if (_equals(other)) 0 else -1;
+ def unsafeCompareTo(other: Value): Int = if (internalEquals(other)) 0 else -1;
- def _equals(other: Value): Boolean = {
+ def internalEquals(other: Value): Boolean = {
other.isInstanceOf[X] &&
other.asInstanceOf[X].blobs.zip(blobs).map(t => t._1 == t._2).reduce(_ && _)
}
diff --git a/commons/pom.xml b/commons/pom.xml
new file mode 100644
index 00000000..0a1ddf85
--- /dev/null
+++ b/commons/pom.xml
@@ -0,0 +1,23 @@
+
+
+
+ parent
+ cn.pandadb
+ 0.0.2
+ ../
+
+ 4.0.0
+
+ cn.pandadb
+ commons
+
+
+
+ junit
+ junit
+
+
+
+
\ No newline at end of file
diff --git a/src/blob/resources/mime.properties b/commons/src/main/resources/mime.properties
similarity index 100%
rename from src/blob/resources/mime.properties
rename to commons/src/main/resources/mime.properties
diff --git a/src/graiph-database/scala/cn/graiph/PropertyExtractor.scala b/commons/src/main/scala/cn/pandadb/cypherplus/PropertyExtractor.scala
similarity index 75%
rename from src/graiph-database/scala/cn/graiph/PropertyExtractor.scala
rename to commons/src/main/scala/cn/pandadb/cypherplus/PropertyExtractor.scala
index 950014aa..a2990197 100644
--- a/src/graiph-database/scala/cn/graiph/PropertyExtractor.scala
+++ b/commons/src/main/scala/cn/pandadb/cypherplus/PropertyExtractor.scala
@@ -1,6 +1,6 @@
-package cn.graiph
+package cn.pandadb.cypherplus
-import cn.graiph.util.Configuration
+import cn.pandadb.util.{Configuration}
/**
* Created by bluejoe on 2019/7/22.
diff --git a/commons/src/main/scala/cn/pandadb/cypherplus/comparators.scala b/commons/src/main/scala/cn/pandadb/cypherplus/comparators.scala
new file mode 100644
index 00000000..a2f5d5f8
--- /dev/null
+++ b/commons/src/main/scala/cn/pandadb/cypherplus/comparators.scala
@@ -0,0 +1,40 @@
+package cn.pandadb.cypherplus
+
+import cn.pandadb.util.{Configuration}
+
+trait AnyComparator {
+ def initialize(conf: Configuration);
+}
+
+trait ValueComparator extends AnyComparator {
+ def compare(a: Any, b: Any): Double;
+}
+
+trait SetComparator extends AnyComparator {
+ def compareAsSets(a: Any, b: Any): Array[Array[Double]];
+}
+
+/**
+ * Created by bluejoe on 2019/1/31.
+ */
+trait CustomPropertyProvider {
+ def getCustomProperty(x: Any, propertyName: String): Option[Any];
+}
+
+trait ValueMatcher {
+ def like(a: Any, b: Any, algoName: Option[String], threshold: Option[Double]): Option[Boolean];
+
+ def containsOne(a: Any, b: Any, algoName: Option[String], threshold: Option[Double]): Option[Boolean];
+
+ def containsSet(a: Any, b: Any, algoName: Option[String], threshold: Option[Double]): Option[Boolean];
+
+ /**
+ * compares two values
+ */
+ def compareOne(a: Any, b: Any, algoName: Option[String]): Option[Double];
+
+ /**
+ * compares two objects as sets
+ */
+ def compareSet(a: Any, b: Any, algoName: Option[String]): Option[Array[Array[Double]]];
+}
diff --git a/commons/src/main/scala/cn/pandadb/cypherplus/utils/CypherPlusUtils.scala b/commons/src/main/scala/cn/pandadb/cypherplus/utils/CypherPlusUtils.scala
new file mode 100644
index 00000000..f4eb8fa1
--- /dev/null
+++ b/commons/src/main/scala/cn/pandadb/cypherplus/utils/CypherPlusUtils.scala
@@ -0,0 +1,24 @@
+package cn.pandadb.cypherplus.utils
+
+import java.util.Locale
+
+/**
+ * @Author: Airzihao
+ * @Description:
+ * @Date: Created at 14:50 2019/11/27
+ * @Modified By:
+ */
+object CypherPlusUtils {
+
+ def isWriteStatement(cypherStr: String): Boolean = {
+ val lowerCypher = cypherStr.toLowerCase(Locale.ROOT)
+ if (lowerCypher.contains("explain")) {
+ false
+ } else if (lowerCypher.contains("create") || lowerCypher.contains("merge") ||
+ lowerCypher.contains("set") || lowerCypher.contains("delete")) {
+ true
+ } else {
+ false
+ }
+ }
+}
diff --git a/src/externel-properties/scala/cn/graiph/util/Configuration.scala b/commons/src/main/scala/cn/pandadb/util/Configuration.scala
similarity index 72%
rename from src/externel-properties/scala/cn/graiph/util/Configuration.scala
rename to commons/src/main/scala/cn/pandadb/util/Configuration.scala
index 049e7545..c56bca0c 100644
--- a/src/externel-properties/scala/cn/graiph/util/Configuration.scala
+++ b/commons/src/main/scala/cn/pandadb/util/Configuration.scala
@@ -18,7 +18,7 @@
* along with this program. If not, see .
*/
-package cn.graiph.util
+package cn.pandadb.util
import java.io.File
@@ -32,7 +32,7 @@ trait Configuration {
/**
* Created by bluejoe on 2018/11/3.
*/
-class ConfigurationEx(conf: Configuration) extends Logging {
+class ConfigurationOps(conf: Configuration) extends Logging {
def getRequiredValueAsString(key: String): String = {
getRequiredValue(key, (x) => x);
}
@@ -68,25 +68,27 @@ class ConfigurationEx(conf: Configuration) extends Logging {
}
}
- def getValueAsString(key: String, defaultValue: String) =
+ def getValueAsString(key: String, defaultValue: String): String =
getValueWithDefault(key, () => defaultValue, (x: String) => x)
- def getValueAsClass(key: String, defaultValue: Class[_]) =
+ def getValueAsClass(key: String, defaultValue: Class[_]): Class[_] =
getValueWithDefault(key, () => defaultValue, (x: String) => Class.forName(x))
- def getValueAsInt(key: String, defaultValue: Int) =
+ def getValueAsInt(key: String, defaultValue: Int): Int =
getValueWithDefault[Int](key, () => defaultValue, (x: String) => x.toInt)
- def getValueAsBoolean(key: String, defaultValue: Boolean) =
+ def getValueAsBoolean(key: String, defaultValue: Boolean): Boolean =
getValueWithDefault[Boolean](key, () => defaultValue, (x: String) => x.toBoolean)
- def getAsFile(key: String, baseDir: File, defaultValue: File) = {
+ def getAsFile(key: String, baseDir: File, defaultValue: File): File = {
getValueWithDefault(key, () => defaultValue, { x =>
val file = new File(x);
- if (file.isAbsolute)
- file;
- else
- new File(baseDir, x);
+ if (file.isAbsolute) {
+ file
+ }
+ else {
+ new File(baseDir, x)
+ }
});
}
}
@@ -102,5 +104,13 @@ class WrongArgumentException(key: String, value: String, clazz: Class[_]) extend
}
object ConfigUtils {
- implicit def config2Ex(conf: Configuration) = new ConfigurationEx(conf);
-}
+ implicit def configOps(conf: Configuration): ConfigurationOps = new ConfigurationOps(conf);
+
+ implicit def mapOps(map: Map[String, String]): ConfigurationOps = new ConfigurationOps(map2Config(map));
+
+ implicit def contextMapOps(conf: ContextMap): ConfigurationOps = new ConfigurationOps(conf.toConfiguration);
+
+ implicit def map2Config(map: Map[String, String]): Configuration = new Configuration() {
+ override def getRaw(name: String): Option[String] = map.get(name)
+ }
+}
\ No newline at end of file
diff --git a/commons/src/main/scala/cn/pandadb/util/ContextMap.scala b/commons/src/main/scala/cn/pandadb/util/ContextMap.scala
new file mode 100644
index 00000000..eb358062
--- /dev/null
+++ b/commons/src/main/scala/cn/pandadb/util/ContextMap.scala
@@ -0,0 +1,41 @@
+package cn.pandadb.util
+
+import scala.collection.Set
+import scala.collection.mutable.{Map => MMap}
+
+class ContextMap {
+ private val _map = MMap[String, Any]();
+
+ def keys: Set[String] = _map.keySet;
+
+ protected def put[T](key: String, value: T): T = {
+ _map(key) = value
+ value
+ }
+
+ protected def put[T](value: T)(implicit manifest: Manifest[T]): T = put[T](manifest.runtimeClass.getName, value)
+
+ protected def get[T](key: String): T = {
+ _map(key).asInstanceOf[T]
+ }
+
+ protected def getOption[T](key: String): Option[T] = _map.get(key).map(_.asInstanceOf[T]);
+
+ protected def get[T]()(implicit manifest: Manifest[T]): T = get(manifest.runtimeClass.getName);
+
+ protected def getOption[T]()(implicit manifest: Manifest[T]): Option[T] = getOption(manifest.runtimeClass.getName);
+
+ def toConfiguration: Configuration = new Configuration() {
+ override def getRaw(name: String): Option[String] = getOption(name)
+ }
+}
+
+object GlobalContext extends ContextMap {
+ def setLeaderNode(f: Boolean): Unit = super.put("isLeaderNode", f)
+
+ def setWatchDog(f: Boolean): Unit = super.put("isWatchDog", f)
+
+ def isWatchDog(): Boolean = super.getOption[Boolean]("isWatchDog").getOrElse(false)
+
+ def isLeaderNode(): Boolean = super.getOption[Boolean]("isLeaderNode").getOrElse(false)
+}
\ No newline at end of file
diff --git a/src/externel-properties/scala/cn/graiph/util/Logging.scala b/commons/src/main/scala/cn/pandadb/util/Logging.scala
similarity index 89%
rename from src/externel-properties/scala/cn/graiph/util/Logging.scala
rename to commons/src/main/scala/cn/pandadb/util/Logging.scala
index 5fdcf856..4ff3182e 100644
--- a/src/externel-properties/scala/cn/graiph/util/Logging.scala
+++ b/commons/src/main/scala/cn/pandadb/util/Logging.scala
@@ -1,4 +1,4 @@
-package cn.graiph.util
+package cn.pandadb.util
/**
* Created by bluejoe on 2019/10/9.
diff --git a/commons/src/main/scala/cn/pandadb/util/PandaException.scala b/commons/src/main/scala/cn/pandadb/util/PandaException.scala
new file mode 100644
index 00000000..20abfd2d
--- /dev/null
+++ b/commons/src/main/scala/cn/pandadb/util/PandaException.scala
@@ -0,0 +1,8 @@
+package cn.pandadb.util
+
+/**
+ * Created by bluejoe on 2019/11/22.
+ */
+class PandaException(msg: String, cause: Throwable = null) extends RuntimeException(msg, cause) {
+
+}
diff --git a/src/externel-properties/scala/cn/graiph/util/ReflectUtils.scala b/commons/src/main/scala/cn/pandadb/util/ReflectUtils.scala
similarity index 93%
rename from src/externel-properties/scala/cn/graiph/util/ReflectUtils.scala
rename to commons/src/main/scala/cn/pandadb/util/ReflectUtils.scala
index 4bfa7ef8..e4c4bbfa 100644
--- a/src/externel-properties/scala/cn/graiph/util/ReflectUtils.scala
+++ b/commons/src/main/scala/cn/pandadb/util/ReflectUtils.scala
@@ -17,7 +17,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-package cn.graiph.util
+package cn.pandadb.util
import java.lang.reflect.Field
@@ -39,7 +39,7 @@ object ReflectUtils {
constructor.newInstance(args.map(_.asInstanceOf[Object]): _*).asInstanceOf[T];
}
- def instanceOf(className: String)(args: Any*) = {
+ def instanceOf(className: String)(args: Any*): Any = {
val constructor = Class.forName(className).getDeclaredConstructor(args.map(_.getClass): _*);
constructor.setAccessible(true);
constructor.newInstance(args.map(_.asInstanceOf[Object]): _*);
@@ -71,8 +71,9 @@ class ReflectedObject(o: AnyRef) {
catch {
case e: NoSuchFieldException =>
val sc = clazz.getSuperclass;
- if (sc == null)
+ if (sc == null) {
throw e;
+ }
_getField(sc, fieldName);
}
@@ -93,6 +94,6 @@ class ReflectedObject(o: AnyRef) {
}
class InvalidFieldPathException(o: AnyRef, path: String, cause: Throwable)
- extends RuntimeException(s"invalid field path: $path, host: $o", cause) {
+ extends PandaException(s"invalid field path: $path, host: $o", cause) {
}
\ No newline at end of file
diff --git a/src/externel-properties/scala/cn/graiph/util/StreamUtils.scala b/commons/src/main/scala/cn/pandadb/util/StreamUtils.scala
similarity index 92%
rename from src/externel-properties/scala/cn/graiph/util/StreamUtils.scala
rename to commons/src/main/scala/cn/pandadb/util/StreamUtils.scala
index e63fa966..d16df2ca 100644
--- a/src/externel-properties/scala/cn/graiph/util/StreamUtils.scala
+++ b/commons/src/main/scala/cn/pandadb/util/StreamUtils.scala
@@ -18,7 +18,7 @@
* along with this program. If not, see .
*/
-package cn.graiph.util
+package cn.pandadb.util
import java.io.{ByteArrayInputStream, ByteArrayOutputStream, InputStream, OutputStream}
@@ -49,9 +49,9 @@ object StreamUtils {
baos.readLong()
}
- implicit def inputStream2Ex(is: InputStream) = new InputStreamEx(is);
+ implicit def inputStream2Ex(is: InputStream): InputStreamEx = new InputStreamEx(is);
- implicit def outputStream2Ex(os: OutputStream) = new OutputStreamEx(os);
+ implicit def outputStream2Ex(os: OutputStream): OutputStreamEx = new OutputStreamEx(os);
}
class InputStreamEx(is: InputStream) {
@@ -76,8 +76,9 @@ class InputStreamEx(is: InputStream) {
val bytes: Array[Byte] = new Array[Byte](n).map(x => 0.toByte);
val nread = is.read(bytes);
- if (nread != n)
+ if (nread != n) {
throw new InsufficientBytesException(n, nread);
+ }
bytes;
}
diff --git a/commons/src/main/scala/cn/pandadb/util/event.scala b/commons/src/main/scala/cn/pandadb/util/event.scala
new file mode 100644
index 00000000..320598a3
--- /dev/null
+++ b/commons/src/main/scala/cn/pandadb/util/event.scala
@@ -0,0 +1,17 @@
+package cn.pandadb.util
+
+import scala.collection.mutable.ArrayBuffer
+
+trait PandaEvent {
+}
+
+object PandaEventHub {
+ type PandaEventHandler = PartialFunction[PandaEvent, Unit];
+ val handlers = ArrayBuffer[PandaEventHandler]();
+
+ def trigger(handler: PandaEventHandler): Unit = handlers += handler;
+
+ def publish(event: PandaEvent): Unit = {
+ handlers.filter(_.isDefinedAt(event)).foreach(_.apply(event))
+ }
+}
\ No newline at end of file
diff --git a/commons/src/main/scala/cn/pandadb/util/module.scala b/commons/src/main/scala/cn/pandadb/util/module.scala
new file mode 100644
index 00000000..c2fee155
--- /dev/null
+++ b/commons/src/main/scala/cn/pandadb/util/module.scala
@@ -0,0 +1,36 @@
+package cn.pandadb.util
+
+import java.io.File
+
+import scala.collection.mutable.ArrayBuffer
+
+trait ClosableModuleComponent {
+ def start(ctx: PandaModuleContext): Unit
+
+ def close(ctx: PandaModuleContext): Unit
+}
+
+trait PandaModule extends ClosableModuleComponent {
+ def init(ctx: PandaModuleContext);
+}
+
+case class PandaModuleContext(configuration: Configuration, storeDir: File, sharedContext: ContextMap) {
+}
+
+class PandaModules extends Logging {
+ val modules = ArrayBuffer[PandaModule]();
+
+ def add(module: PandaModule): PandaModules = {
+ modules += module
+ this
+ }
+
+ def init(ctx: PandaModuleContext): Unit = modules.foreach { module =>
+ module.init(ctx)
+ logger.info(s"initialized ${module.getClass.getSimpleName}")
+ }
+
+ def start(ctx: PandaModuleContext): Unit = modules.foreach(_.start(ctx))
+
+ def close(ctx: PandaModuleContext): Unit = modules.foreach(_.close(ctx))
+}
\ No newline at end of file
diff --git a/commons/src/test/scala/CypherPlusUtilsTest.scala b/commons/src/test/scala/CypherPlusUtilsTest.scala
new file mode 100644
index 00000000..9a4cd974
--- /dev/null
+++ b/commons/src/test/scala/CypherPlusUtilsTest.scala
@@ -0,0 +1,50 @@
+import cn.pandadb.cypherplus.utils.CypherPlusUtils
+import org.junit.runners.MethodSorters
+import org.junit.{Assert, FixMethodOrder, Test}
+
+import scala.collection.mutable.ListBuffer
+
+/**
+ * @Author: Airzihao
+ * @Description:
+ * @Date: Created at 17:19 2019/12/7
+ * @Modified By:
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class CypherPlusUtilsTest {
+ var writeStatements: ListBuffer[String] = new ListBuffer[String]
+ var notWriteStatements: ListBuffer[String] = new ListBuffer[String]
+
+ notWriteStatements.append("EXPlain Create(n:Test)")
+ notWriteStatements.append("Explain Match(n:T) Delete n;")
+ notWriteStatements.append("expLaIN Match(n) set n.name='panda'")
+ notWriteStatements.append("Match(n) \n with n \n Return n.name")
+
+ writeStatements.append("Create(n:Test{prop:'prop'})")
+ writeStatements.append("Merge(n:T{name:'panda'})")
+ writeStatements.append("Match(n:Test) sET n.prop=123")
+ writeStatements.append("Match(n) Where n.prop=123 DeLETe n")
+
+ @Test
+ def test1(): Unit = {
+ writeStatements.toList.foreach(statement => {
+ if (!CypherPlusUtils.isWriteStatement(statement)) {
+ // scalastyle:off
+ println(s"error: ${statement} judged as a not-write statement.")
+ }
+ Assert.assertEquals(true, CypherPlusUtils.isWriteStatement(statement))
+ })
+ }
+
+ @Test
+ def test2(): Unit = {
+ notWriteStatements.toList.foreach(statement => {
+ if (CypherPlusUtils.isWriteStatement(statement)) {
+ // scalastyle:off
+ println(s"error: ${statement} judged as a write statement.")
+ }
+ Assert.assertEquals(false, CypherPlusUtils.isWriteStatement(statement))
+ })
+ }
+
+}
diff --git a/testdata/cypher-plugins.xml b/cypher-plugins.xml
similarity index 82%
rename from testdata/cypher-plugins.xml
rename to cypher-plugins.xml
index f73a8f2b..d7f018f3 100644
--- a/testdata/cypher-plugins.xml
+++ b/cypher-plugins.xml
@@ -3,47 +3,47 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -54,7 +54,7 @@
-
+
@@ -62,7 +62,7 @@
-
+
@@ -70,7 +70,7 @@
-
+
@@ -78,7 +78,7 @@
-
+
diff --git a/docs/codespec.md b/docs/codespec.md
index 73cf05f0..c70693cc 100644
--- a/docs/codespec.md
+++ b/docs/codespec.md
@@ -13,6 +13,7 @@
### Rule 3: Do not keep useless comments!!!
> * wrong comments/validated comments will lead people to a wrong way!
> * please write your comments in English!
+> * pay attention to blank lines, remove unnecessary blank lines please.
### Rule 4: Use s"" to format string
> * use: `s"hello, $name"`, do not use: `"hello, "+name`
@@ -24,6 +25,7 @@
### Rule 6: Write test cases
> * write JUnit test cases, instead of write a Java program
> * separate test source code with main source code
+> * Use `Assert`, don't judge manually.
### Rule 7: NO hard coding
> * NO magic numbers
@@ -38,4 +40,4 @@
### Rule 9: Please limit maximum line length
-> * Limit all lines to a maximum of 79 characters.
\ No newline at end of file
+> * Limit all lines to a maximum of 79 characters.
diff --git a/docs/logo.png b/docs/logo.png
new file mode 100644
index 00000000..4daa62c7
Binary files /dev/null and b/docs/logo.png differ
diff --git a/external-properties/pom.xml b/external-properties/pom.xml
new file mode 100644
index 00000000..e5945a02
--- /dev/null
+++ b/external-properties/pom.xml
@@ -0,0 +1,73 @@
+
+
+
+ parent
+ cn.pandadb
+ 0.0.2
+ ../
+
+ 4.0.0
+
+ cn.pandadb
+ external-properties
+
+
+
+ cn.pandadb
+ commons
+ ${pandadb.version}
+ compile
+
+
+ cn.pandadb
+ network-commons
+ ${pandadb.version}
+ compile
+
+
+ cn.pandadb
+ neo4j-hacking
+ ${pandadb.version}
+ compile
+
+
+ org.apache.solr
+ solr-solrj
+
+
+ org.elasticsearch.client
+ elasticsearch-rest-high-level-client
+ 6.5.0
+
+
+
+ com.alibaba
+ fastjson
+ 1.2.62
+
+
+
+
+
+
+
+ net.alchim31.maven
+ scala-maven-plugin
+ 3.2.1
+
+
+ scala-compile-first
+ process-resources
+
+ add-source
+ compile
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/external-properties/src/main/java/org/neo4j/kernel/impl/api/KernelTransactionImplementation.java b/external-properties/src/main/java/org/neo4j/kernel/impl/api/KernelTransactionImplementation.java
new file mode 100644
index 00000000..7c4e80c3
--- /dev/null
+++ b/external-properties/src/main/java/org/neo4j/kernel/impl/api/KernelTransactionImplementation.java
@@ -0,0 +1,1308 @@
+/*
+ * Copyright (c) 2002-2019 "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.impl.api;
+
+import java.time.Clock;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.function.Function;
+import java.util.stream.Stream;
+
+import org.neo4j.collection.pool.Pool;
+import org.neo4j.graphdb.NotInTransactionException;
+import org.neo4j.graphdb.TransactionTerminatedException;
+import org.neo4j.internal.kernel.api.CursorFactory;
+import org.neo4j.internal.kernel.api.ExecutionStatistics;
+import org.neo4j.internal.kernel.api.ExplicitIndexRead;
+import org.neo4j.internal.kernel.api.ExplicitIndexWrite;
+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.RelationshipScanCursor;
+import org.neo4j.internal.kernel.api.SchemaRead;
+import org.neo4j.internal.kernel.api.SchemaWrite;
+import org.neo4j.internal.kernel.api.Token;
+import org.neo4j.internal.kernel.api.TokenRead;
+import org.neo4j.internal.kernel.api.TokenWrite;
+import org.neo4j.internal.kernel.api.Write;
+import org.neo4j.internal.kernel.api.exceptions.InvalidTransactionTypeKernelException;
+import org.neo4j.internal.kernel.api.exceptions.TransactionFailureException;
+import org.neo4j.internal.kernel.api.exceptions.schema.ConstraintValidationException;
+import org.neo4j.internal.kernel.api.exceptions.schema.CreateConstraintFailureException;
+import org.neo4j.internal.kernel.api.exceptions.schema.SchemaKernelException;
+import org.neo4j.internal.kernel.api.schema.SchemaDescriptor;
+import org.neo4j.internal.kernel.api.security.AccessMode;
+import org.neo4j.internal.kernel.api.security.AuthSubject;
+import org.neo4j.internal.kernel.api.security.SecurityContext;
+import org.neo4j.io.pagecache.tracing.cursor.PageCursorTracer;
+import org.neo4j.io.pagecache.tracing.cursor.PageCursorTracerSupplier;
+import org.neo4j.io.pagecache.tracing.cursor.context.VersionContextSupplier;
+import org.neo4j.kernel.api.KernelTransaction;
+import org.neo4j.kernel.api.SilentTokenNameLookup;
+import org.neo4j.kernel.api.exceptions.ConstraintViolationTransactionFailureException;
+import org.neo4j.kernel.api.exceptions.Status;
+import org.neo4j.kernel.api.explicitindex.AutoIndexing;
+import org.neo4j.kernel.api.txstate.ExplicitIndexTransactionState;
+import org.neo4j.kernel.api.txstate.TransactionState;
+import org.neo4j.kernel.api.txstate.TxStateHolder;
+import org.neo4j.kernel.api.txstate.auxiliary.AuxiliaryTransactionState;
+import org.neo4j.kernel.api.txstate.auxiliary.AuxiliaryTransactionStateCloseException;
+import org.neo4j.kernel.api.txstate.auxiliary.AuxiliaryTransactionStateHolder;
+import org.neo4j.kernel.api.txstate.auxiliary.AuxiliaryTransactionStateManager;
+import org.neo4j.kernel.configuration.Config;
+import org.neo4j.kernel.impl.api.index.IndexingService;
+import org.neo4j.kernel.impl.api.state.ConstraintIndexCreator;
+import org.neo4j.kernel.impl.api.state.TxState;
+import org.neo4j.kernel.impl.constraints.ConstraintSemantics;
+import org.neo4j.kernel.impl.core.TokenHolders;
+import org.neo4j.kernel.impl.factory.AccessCapability;
+import org.neo4j.kernel.impl.index.ExplicitIndexStore;
+import org.neo4j.kernel.impl.locking.ActiveLock;
+import org.neo4j.kernel.impl.locking.Locks;
+import org.neo4j.kernel.impl.locking.StatementLocks;
+import org.neo4j.kernel.impl.newapi.AllStoreHolder;
+import org.neo4j.kernel.impl.newapi.DefaultCursors;
+import org.neo4j.kernel.impl.newapi.IndexTxStateUpdater;
+import org.neo4j.kernel.impl.newapi.KernelToken;
+import org.neo4j.kernel.impl.newapi.Operations;
+import org.neo4j.kernel.impl.proc.Procedures;
+import org.neo4j.kernel.impl.transaction.TransactionHeaderInformationFactory;
+import org.neo4j.kernel.impl.transaction.TransactionMonitor;
+import org.neo4j.kernel.impl.transaction.log.PhysicalTransactionRepresentation;
+import org.neo4j.kernel.impl.transaction.tracing.CommitEvent;
+import org.neo4j.kernel.impl.transaction.tracing.TransactionEvent;
+import org.neo4j.kernel.impl.transaction.tracing.TransactionTracer;
+import org.neo4j.kernel.impl.util.Dependencies;
+import org.neo4j.kernel.impl.util.collection.CollectionsFactory;
+import org.neo4j.kernel.impl.util.collection.CollectionsFactorySupplier;
+import org.neo4j.resources.CpuClock;
+import org.neo4j.resources.HeapAllocation;
+import org.neo4j.storageengine.api.StorageCommand;
+import org.neo4j.storageengine.api.StorageEngine;
+import org.neo4j.storageengine.api.StorageReader;
+import org.neo4j.storageengine.api.lock.LockTracer;
+import org.neo4j.storageengine.api.schema.IndexDescriptor;
+import org.neo4j.storageengine.api.txstate.TxStateVisitor;
+
+import static java.lang.String.format;
+import static java.util.Collections.emptyMap;
+import static java.util.concurrent.TimeUnit.NANOSECONDS;
+import static org.neo4j.storageengine.api.TransactionApplicationMode.INTERNAL;
+
+/**
+ * This class should replace the {@link org.neo4j.kernel.api.KernelTransaction} interface, and take its name, as soon
+ * as
+ * {@code TransitionalTxManagementKernelTransaction} is gone from {@code server}.
+ */
+public class KernelTransactionImplementation implements KernelTransaction, TxStateHolder, ExecutionStatistics
+{
+ /*
+ * IMPORTANT:
+ * This class is pooled and re-used. If you add *any* state to it, you *must* make sure that:
+ * - the #initialize() method resets that state for re-use
+ * - the #release() method releases resources acquired in #initialize() or during the transaction's life time
+ */
+
+ // default values for not committed tx id and tx commit time
+ private static final long NOT_COMMITTED_TRANSACTION_ID = -1;
+ private static final long NOT_COMMITTED_TRANSACTION_COMMIT_TIME = -1;
+
+ private final CollectionsFactory collectionsFactory;
+
+ // Logic
+ private final SchemaWriteGuard schemaWriteGuard;
+ private final TransactionHooks hooks;
+ private final ConstraintIndexCreator constraintIndexCreator;
+ private final StorageEngine storageEngine;
+ private final TransactionTracer transactionTracer;
+ private final Pool pool;
+ private final AuxiliaryTransactionStateManager auxTxStateManager;
+
+ // For committing
+ private final TransactionHeaderInformationFactory headerInformationFactory;
+ private final TransactionCommitProcess commitProcess;
+ private final TransactionMonitor transactionMonitor;
+ private final PageCursorTracerSupplier cursorTracerSupplier;
+ private final VersionContextSupplier versionContextSupplier;
+ private final StorageReader storageReader;
+ private final ClockContext clocks;
+ private final AccessCapability accessCapability;
+ private final ConstraintSemantics constraintSemantics;
+
+ // State that needs to be reset between uses. Most of these should be cleared or released in #release(),
+ // whereas others, such as timestamp or txId when transaction starts, even locks, needs to be set in #initialize().
+ private TxState txState;
+ private AuxiliaryTransactionStateHolder auxTxStateHolder;
+ private volatile TransactionWriteState writeState;
+ private TransactionHooks.TransactionHooksState hooksState;
+ private final KernelStatement currentStatement;
+ private final List closeListeners = new ArrayList<>( 2 );
+ private SecurityContext securityContext;
+ private volatile StatementLocks statementLocks;
+ private volatile long userTransactionId;
+ private boolean beforeHookInvoked;
+ private volatile boolean closing;
+ private volatile boolean closed;
+ private boolean failure;
+ private boolean success;
+ private volatile Status terminationReason;
+ private long startTimeMillis;
+ private long timeoutMillis;
+ private long lastTransactionIdWhenStarted;
+ private volatile long lastTransactionTimestampWhenStarted;
+ private final Statistics statistics;
+ private TransactionEvent transactionEvent;
+ private Type type;
+ private long transactionId;
+ private long commitTime;
+ private volatile int reuseCount;
+ private volatile Map userMetaData;
+ private final Operations operations;
+
+ /**
+ * Lock prevents transaction {@link #markForTermination(Status)} transaction termination} from interfering with
+ * {@link #close() transaction commit} and specifically with {@link #release()}.
+ * Termination can run concurrently with commit and we need to make sure that it terminates the right lock client
+ * and the right transaction (with the right {@link #reuseCount}) because {@link KernelTransactionImplementation}
+ * instances are pooled.
+ */
+ private final Lock terminationReleaseLock = new ReentrantLock();
+
+ public KernelTransactionImplementation( Config config, StatementOperationParts statementOperations, SchemaWriteGuard schemaWriteGuard,
+ TransactionHooks hooks, ConstraintIndexCreator constraintIndexCreator, Procedures procedures,
+ TransactionHeaderInformationFactory headerInformationFactory, TransactionCommitProcess commitProcess, TransactionMonitor transactionMonitor,
+ AuxiliaryTransactionStateManager auxTxStateManager, Pool pool, Clock clock,
+ AtomicReference cpuClockRef, AtomicReference heapAllocationRef, TransactionTracer transactionTracer,
+ LockTracer lockTracer, PageCursorTracerSupplier cursorTracerSupplier, StorageEngine storageEngine, AccessCapability accessCapability,
+ AutoIndexing autoIndexing, ExplicitIndexStore explicitIndexStore, VersionContextSupplier versionContextSupplier,
+ CollectionsFactorySupplier collectionsFactorySupplier, ConstraintSemantics constraintSemantics, SchemaState schemaState,
+ IndexingService indexingService, TokenHolders tokenHolders, Dependencies dataSourceDependencies )
+ {
+ this.schemaWriteGuard = schemaWriteGuard;
+ this.hooks = hooks;
+ this.constraintIndexCreator = constraintIndexCreator;
+ this.headerInformationFactory = headerInformationFactory;
+ this.commitProcess = commitProcess;
+ this.transactionMonitor = transactionMonitor;
+ this.storageReader = storageEngine.newReader();
+ this.storageEngine = storageEngine;
+ this.auxTxStateManager = auxTxStateManager;
+ this.pool = pool;
+ this.clocks = new ClockContext( clock );
+ this.transactionTracer = transactionTracer;
+ this.cursorTracerSupplier = cursorTracerSupplier;
+ this.versionContextSupplier = versionContextSupplier;
+ this.currentStatement = new KernelStatement( this, this, storageReader,
+ lockTracer, statementOperations, this.clocks,
+ versionContextSupplier );
+ this.accessCapability = accessCapability;
+ this.statistics = new Statistics( this, cpuClockRef, heapAllocationRef );
+ this.userMetaData = emptyMap();
+ this.constraintSemantics = constraintSemantics;
+ DefaultCursors cursors = new DefaultCursors( storageReader );
+ AllStoreHolder allStoreHolder =
+ new AllStoreHolder( storageReader, this, cursors, explicitIndexStore,
+ procedures, schemaState, dataSourceDependencies );
+ this.operations =
+ new Operations(
+ allStoreHolder,
+ new IndexTxStateUpdater( allStoreHolder, indexingService ), storageReader,
+ this,
+ new KernelToken( storageReader, this, tokenHolders ),
+ cursors,
+ autoIndexing,
+ constraintIndexCreator,
+ constraintSemantics,
+ indexingService,
+ config );
+ this.collectionsFactory = collectionsFactorySupplier.create();
+ }
+
+ /**
+ * Reset this transaction to a vanilla state, turning it into a logically new transaction.
+ */
+ public KernelTransactionImplementation initialize( long lastCommittedTx, long lastTimeStamp, StatementLocks statementLocks, Type type,
+ SecurityContext frozenSecurityContext, long transactionTimeout, long userTransactionId )
+ {
+ this.type = type;
+ this.statementLocks = statementLocks;
+ this.userTransactionId = userTransactionId;
+ this.terminationReason = null;
+ this.closing = false;
+ this.closed = false;
+ this.beforeHookInvoked = false;
+ this.failure = false;
+ this.success = false;
+ this.writeState = TransactionWriteState.NONE;
+ this.startTimeMillis = clocks.systemClock().millis();
+ this.timeoutMillis = transactionTimeout;
+ this.lastTransactionIdWhenStarted = lastCommittedTx;
+ this.lastTransactionTimestampWhenStarted = lastTimeStamp;
+ this.transactionEvent = transactionTracer.beginTransaction();
+ assert transactionEvent != null : "transactionEvent was null!";
+ this.securityContext = frozenSecurityContext;
+ this.transactionId = NOT_COMMITTED_TRANSACTION_ID;
+ this.commitTime = NOT_COMMITTED_TRANSACTION_COMMIT_TIME;
+ PageCursorTracer pageCursorTracer = cursorTracerSupplier.get();
+ this.statistics.init( Thread.currentThread().getId(), pageCursorTracer );
+ this.currentStatement.initialize( statementLocks, pageCursorTracer );
+ this.operations.initialize();
+ return this;
+ }
+
+ int getReuseCount()
+ {
+ return reuseCount;
+ }
+
+ @Override
+ public long startTime()
+ {
+ return startTimeMillis;
+ }
+
+ @Override
+ public long timeout()
+ {
+ return timeoutMillis;
+ }
+
+ @Override
+ public long lastTransactionIdWhenStarted()
+ {
+ return lastTransactionIdWhenStarted;
+ }
+
+ @Override
+ public void success()
+ {
+ this.success = true;
+ }
+
+ boolean isSuccess()
+ {
+ return success;
+ }
+
+ @Override
+ public void failure()
+ {
+ failure = true;
+ }
+
+ @Override
+ public Optional getReasonIfTerminated()
+ {
+ return Optional.ofNullable( terminationReason );
+ }
+
+ boolean markForTermination( long expectedReuseCount, Status reason )
+ {
+ terminationReleaseLock.lock();
+ try
+ {
+ return expectedReuseCount == reuseCount && markForTerminationIfPossible( reason );
+ }
+ finally
+ {
+ terminationReleaseLock.unlock();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * This method is guarded by {@link #terminationReleaseLock} to coordinate concurrent
+ * {@link #close()} and {@link #release()} calls.
+ */
+ @Override
+ public void markForTermination( Status reason )
+ {
+ terminationReleaseLock.lock();
+ try
+ {
+ markForTerminationIfPossible( reason );
+ }
+ finally
+ {
+ terminationReleaseLock.unlock();
+ }
+ }
+
+ @Override
+ public boolean isSchemaTransaction()
+ {
+ return writeState == TransactionWriteState.SCHEMA;
+ }
+
+ private boolean markForTerminationIfPossible( Status reason )
+ {
+ if ( canBeTerminated() )
+ {
+ failure = true;
+ terminationReason = reason;
+ if ( statementLocks != null )
+ {
+ statementLocks.stop();
+ }
+ transactionMonitor.transactionTerminated( hasTxStateWithChanges() );
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean isOpen()
+ {
+ return !closed && !closing;
+ }
+
+ @Override
+ public SecurityContext securityContext()
+ {
+ if ( securityContext == null )
+ {
+ throw new NotInTransactionException();
+ }
+ return securityContext;
+ }
+
+ @Override
+ public AuthSubject subjectOrAnonymous()
+ {
+ SecurityContext context = this.securityContext;
+ return context == null ? AuthSubject.ANONYMOUS : context.subject();
+ }
+
+ @Override
+ public void setMetaData( Map data )
+ {
+ this.userMetaData = data;
+ }
+
+ @Override
+ public Map getMetaData()
+ {
+ return userMetaData;
+ }
+
+ @Override
+ public KernelStatement acquireStatement()
+ {
+ assertTransactionOpen();
+ currentStatement.acquire();
+ return currentStatement;
+ }
+
+ @Override
+ public IndexDescriptor indexUniqueCreate( SchemaDescriptor schema, String provider ) throws SchemaKernelException
+ {
+ return operations.indexUniqueCreate( schema, provider );
+ }
+
+ @Override
+ public long pageHits()
+ {
+ return cursorTracerSupplier.get().hits();
+ }
+
+ @Override
+ public long pageFaults()
+ {
+ return cursorTracerSupplier.get().faults();
+ }
+
+ ExecutingQueryList executingQueries()
+ {
+ return currentStatement.executingQueryList();
+ }
+
+ void upgradeToDataWrites() throws InvalidTransactionTypeKernelException
+ {
+ writeState = writeState.upgradeToDataWrites();
+ }
+
+ void upgradeToSchemaWrites() throws InvalidTransactionTypeKernelException
+ {
+ schemaWriteGuard.assertSchemaWritesAllowed();
+ writeState = writeState.upgradeToSchemaWrites();
+ }
+
+ private void dropCreatedConstraintIndexes() throws TransactionFailureException
+ {
+ if ( hasTxStateWithChanges() )
+ {
+ for ( IndexDescriptor createdConstraintIndex : txState().constraintIndexesCreatedInTx() )
+ {
+ // TODO logically, which statement should this operation be performed on?
+ constraintIndexCreator.dropUniquenessConstraintIndex( createdConstraintIndex );
+ }
+ }
+ }
+
+ @Override
+ public TransactionState txState()
+ {
+ if ( txState == null )
+ {
+ transactionMonitor.upgradeToWriteTransaction();
+ txState = new TxState( collectionsFactory );
+ }
+ return txState;
+ }
+
+ private AuxiliaryTransactionStateHolder getAuxTxStateHolder()
+ {
+ if ( auxTxStateHolder == null )
+ {
+ auxTxStateHolder = auxTxStateManager.openStateHolder();
+ }
+ return auxTxStateHolder;
+ }
+
+ @Override
+ public AuxiliaryTransactionState auxiliaryTxState( Object providerIdentityKey )
+ {
+ return getAuxTxStateHolder().getState( providerIdentityKey );
+ }
+
+ @Override
+ public ExplicitIndexTransactionState explicitIndexTxState()
+ {
+ return (ExplicitIndexTransactionState) getAuxTxStateHolder().getState( ExplicitIndexTransactionStateProvider.PROVIDER_KEY );
+ }
+
+ @Override
+ public boolean hasTxStateWithChanges()
+ {
+ return txState != null && txState.hasChanges();
+ }
+
+ private void markAsClosed( long txId )
+ {
+ assertTransactionOpen();
+ closed = true;
+ notifyListeners( txId );
+ closeCurrentStatementIfAny();
+ }
+
+ private void notifyListeners( long txId )
+ {
+ for ( CloseListener closeListener : closeListeners )
+ {
+ closeListener.notify( txId );
+ }
+ }
+
+ private void closeCurrentStatementIfAny()
+ {
+ currentStatement.forceClose();
+ }
+
+ private void assertTransactionNotClosing()
+ {
+ if ( closing )
+ {
+ throw new IllegalStateException( "This transaction is already being closed." );
+ }
+ }
+
+ private void assertTransactionOpen()
+ {
+ if ( closed )
+ {
+ throw new IllegalStateException( "This transaction has already been completed." );
+ }
+ }
+
+ @Override
+ public void assertOpen()
+ {
+ Status reason = this.terminationReason;
+ if ( reason != null )
+ {
+ throw new TransactionTerminatedException( reason );
+ }
+ if ( closed )
+ {
+ throw new NotInTransactionException( "The transaction has been closed." );
+ }
+ }
+
+ private boolean hasChanges()
+ {
+ return hasTxStateWithChanges() || hasAuxTxStateChanges();
+ }
+
+ private boolean hasAuxTxStateChanges()
+ {
+ return auxTxStateHolder != null && getAuxTxStateHolder().hasChanges();
+ }
+
+ private boolean hasDataChanges()
+ {
+ return hasTxStateWithChanges() && txState.hasDataChanges();
+ }
+
+ @Override
+ public long closeTransaction() throws TransactionFailureException
+ {
+ assertTransactionOpen();
+ assertTransactionNotClosing();
+ closing = true;
+ try
+ {
+ if ( failure || !success || isTerminated() )
+ {
+ // NOTE: pandadb
+ this.operations.customPropWriteTx().rollback();
+ // END-NOTE
+ rollback();
+ failOnNonExplicitRollbackIfNeeded();
+ return ROLLBACK;
+ }
+ else
+ {
+ return commit();
+ }
+ }
+ finally
+ {
+ try
+ {
+ // NOTE: pandadb
+ this.operations.customPropWriteTx().close();
+ // END-NOTE
+ closed = true;
+ closing = false;
+ transactionEvent.setSuccess( success );
+ transactionEvent.setFailure( failure );
+ transactionEvent.setTransactionWriteState( writeState.name() );
+ transactionEvent.setReadOnly( txState == null || !txState.hasChanges() );
+ transactionEvent.close();
+ }
+ finally
+ {
+ release();
+ }
+ }
+ }
+
+ public boolean isClosing()
+ {
+ return closing;
+ }
+
+ /**
+ * Throws exception if this transaction was marked as successful but failure flag has also been set to true.
+ *
+ * This could happen when:
+ *
+ * - caller explicitly calls both {@link #success()} and {@link #failure()}
+ * - caller explicitly calls {@link #success()} but transaction execution fails
+ * - caller explicitly calls {@link #success()} but transaction is terminated
+ *
+ *
+ *
+ * @throws TransactionFailureException when execution failed
+ * @throws TransactionTerminatedException when transaction was terminated
+ */
+ private void failOnNonExplicitRollbackIfNeeded() throws TransactionFailureException
+ {
+ if ( success && isTerminated() )
+ {
+ throw new TransactionTerminatedException( terminationReason );
+ }
+ if ( success )
+ {
+ // Success was called, but also failure which means that the client code using this
+ // transaction passed through a happy path, but the transaction was still marked as
+ // failed for one or more reasons. Tell the user that although it looked happy it
+ // wasn't committed, but was instead rolled back.
+ throw new TransactionFailureException( Status.Transaction.TransactionMarkedAsFailed,
+ "Transaction rolled back even if marked as successful" );
+ }
+ }
+
+ private long commit() throws TransactionFailureException
+ {
+ boolean success = false;
+ long txId = READ_ONLY;
+
+ try ( CommitEvent commitEvent = transactionEvent.beginCommitEvent() )
+ {
+ // Trigger transaction "before" hooks.
+ if ( hasDataChanges() )
+ {
+ try
+ {
+ hooksState = hooks.beforeCommit( txState, this, storageReader );
+ if ( hooksState != null && hooksState.failed() )
+ {
+ Throwable cause = hooksState.failure();
+ throw new TransactionFailureException( Status.Transaction.TransactionHookFailed, cause, "" );
+ }
+ }
+ finally
+ {
+ beforeHookInvoked = true;
+ }
+ }
+
+ // Convert changes into commands and commit
+ if ( hasChanges() )
+ {
+ // grab all optimistic locks now, locks can't be deferred any further
+ statementLocks.prepareForCommit( currentStatement.lockTracer() );
+ // use pessimistic locks for the rest of the commit process, locks can't be deferred any further
+ Locks.Client commitLocks = statementLocks.pessimistic();
+
+ // Gather up commands from the various sources
+ Collection extractedCommands = new ArrayList<>();
+ storageEngine.createCommands(
+ extractedCommands,
+ txState, storageReader,
+ commitLocks,
+ lastTransactionIdWhenStarted,
+ this::enforceConstraints );
+ if ( hasAuxTxStateChanges() )
+ {
+ auxTxStateHolder.extractCommands( extractedCommands );
+ }
+
+ /* Here's the deal: we track a quick-to-access hasChanges in transaction state which is true
+ * if there are any changes imposed by this transaction. Some changes made inside a transaction undo
+ * previously made changes in that same transaction, and so at some point a transaction may have
+ * changes and at another point, after more changes seemingly,
+ * the transaction may not have any changes.
+ * However, to track that "undoing" of the changes is a bit tedious, intrusive and hard to maintain
+ * and get right.... So to really make sure the transaction has changes we re-check by looking if we
+ * have produced any commands to add to the logical log.
+ */
+ if ( !extractedCommands.isEmpty() )
+ {
+ // Finish up the whole transaction representation
+ PhysicalTransactionRepresentation transactionRepresentation =
+ new PhysicalTransactionRepresentation( extractedCommands );
+ TransactionHeaderInformation headerInformation = headerInformationFactory.create();
+ long timeCommitted = clocks.systemClock().millis();
+ transactionRepresentation.setHeader( headerInformation.getAdditionalHeader(),
+ headerInformation.getMasterId(),
+ headerInformation.getAuthorId(),
+ startTimeMillis, lastTransactionIdWhenStarted, timeCommitted,
+ commitLocks.getLockSessionId() );
+
+ // Commit the transaction
+ success = true;
+ TransactionToApply batch = new TransactionToApply( transactionRepresentation,
+ versionContextSupplier.getVersionContext() );
+ txId = transactionId = commitProcess.commit( batch, commitEvent, INTERNAL );
+ commitTime = timeCommitted;
+ }
+ }
+ // NOTE: pandadb
+ this.operations.customPropWriteTx().commit();
+ // END-NOTE
+ success = true;
+ return txId;
+ }
+ catch ( ConstraintValidationException | CreateConstraintFailureException e )
+ {
+ throw new ConstraintViolationTransactionFailureException(
+ e.getUserMessage( new SilentTokenNameLookup( tokenRead() ) ), e );
+ }
+ finally
+ {
+ if ( !success )
+ {
+ // NOTE: pandadb
+ this.operations.customPropWriteTx().rollback();
+ // END-NOTE
+ rollback();
+ }
+ else
+ {
+ afterCommit( txId );
+ }
+ }
+ }
+
+ private void rollback() throws TransactionFailureException
+ {
+ try
+ {
+ try
+ {
+ dropCreatedConstraintIndexes();
+ }
+ catch ( IllegalStateException | SecurityException e )
+ {
+ throw new TransactionFailureException( Status.Transaction.TransactionRollbackFailed, e,
+ "Could not drop created constraint indexes" );
+ }
+
+ // Free any acquired id's
+ if ( txState != null )
+ {
+ try
+ {
+ txState.accept( new TxStateVisitor.Adapter()
+ {
+ @Override
+ public void visitCreatedNode( long id )
+ {
+ storageReader.releaseNode( id );
+ }
+
+ @Override
+ public void visitCreatedRelationship( long id, int type, long startNode, long endNode )
+ {
+ storageReader.releaseRelationship( id );
+ }
+ } );
+ }
+ catch ( ConstraintValidationException | CreateConstraintFailureException e )
+ {
+ throw new IllegalStateException(
+ "Releasing locks during rollback should perform no constraints checking.", e );
+ }
+ }
+ }
+ finally
+ {
+ afterRollback();
+ }
+ }
+
+ @Override
+ public Read dataRead()
+ {
+ assertAllows( AccessMode::allowsReads, "Read" );
+ return operations.dataRead();
+ }
+
+ @Override
+ public Write dataWrite() throws InvalidTransactionTypeKernelException
+ {
+ accessCapability.assertCanWrite();
+ assertAllows( AccessMode::allowsWrites, "Write" );
+ upgradeToDataWrites();
+ return operations;
+ }
+
+ @Override
+ public TokenWrite tokenWrite()
+ {
+ accessCapability.assertCanWrite();
+ return operations.token();
+ }
+
+ @Override
+ public Token token()
+ {
+ accessCapability.assertCanWrite();
+ return operations.token();
+ }
+
+ @Override
+ public TokenRead tokenRead()
+ {
+ assertAllows( AccessMode::allowsReads, "Read" );
+ return operations.token();
+ }
+
+ @Override
+ public ExplicitIndexRead indexRead()
+ {
+ assertAllows( AccessMode::allowsReads, "Read" );
+
+ return operations.indexRead();
+ }
+
+ @Override
+ public ExplicitIndexWrite indexWrite() throws InvalidTransactionTypeKernelException
+ {
+ accessCapability.assertCanWrite();
+ assertAllows( AccessMode::allowsWrites, "Write" );
+ upgradeToDataWrites();
+
+ return operations;
+ }
+
+ @Override
+ public SchemaRead schemaRead()
+ {
+ assertAllows( AccessMode::allowsReads, "Read" );
+ return operations.schemaRead();
+ }
+
+ @Override
+ public SchemaWrite schemaWrite() throws InvalidTransactionTypeKernelException
+ {
+ accessCapability.assertCanWrite();
+ assertAllows( AccessMode::allowsSchemaWrites, "Schema" );
+
+ upgradeToSchemaWrites();
+ return operations;
+ }
+
+ @Override
+ public org.neo4j.internal.kernel.api.Locks locks()
+ {
+ return operations.locks();
+ }
+
+ public StatementLocks statementLocks()
+ {
+ assertOpen();
+ return statementLocks;
+ }
+
+ @Override
+ public CursorFactory cursors()
+ {
+ return operations.cursors();
+ }
+
+ @Override
+ public org.neo4j.internal.kernel.api.Procedures procedures()
+ {
+ return operations.procedures();
+ }
+
+ @Override
+ public ExecutionStatistics executionStatistics()
+ {
+ return this;
+ }
+
+ public LockTracer lockTracer()
+ {
+ return currentStatement.lockTracer();
+ }
+
+ public void assertAllows( Function allows, String mode )
+ {
+ AccessMode accessMode = securityContext().mode();
+ if ( !allows.apply( accessMode ) )
+ {
+ throw accessMode.onViolation(
+ format( "%s operations are not allowed for %s.", mode,
+ securityContext().description() ) );
+ }
+ }
+
+ private void afterCommit( long txId )
+ {
+ try
+ {
+ markAsClosed( txId );
+ if ( beforeHookInvoked )
+ {
+ hooks.afterCommit( txState, this, hooksState );
+ }
+ }
+ finally
+ {
+ transactionMonitor.transactionFinished( true, hasTxStateWithChanges() );
+ }
+ }
+
+ private void afterRollback()
+ {
+ try
+ {
+ markAsClosed( ROLLBACK );
+ if ( beforeHookInvoked )
+ {
+ hooks.afterRollback( txState, this, hooksState );
+ }
+ }
+ finally
+ {
+ transactionMonitor.transactionFinished( false, hasTxStateWithChanges() );
+ }
+ }
+
+ /**
+ * Release resources held up by this transaction & return it to the transaction pool.
+ * This method is guarded by {@link #terminationReleaseLock} to coordinate concurrent
+ * {@link #markForTermination(Status)} calls.
+ */
+ private void release()
+ {
+ AuxiliaryTransactionStateCloseException auxStateCloseException = null;
+ terminationReleaseLock.lock();
+ try
+ {
+ statementLocks.close();
+ statementLocks = null;
+ terminationReason = null;
+ type = null;
+ securityContext = null;
+ transactionEvent = null;
+ if ( auxTxStateHolder != null )
+ {
+ auxStateCloseException = closeAuxTxState();
+ }
+ txState = null;
+ collectionsFactory.release();
+ hooksState = null;
+ closeListeners.clear();
+ reuseCount++;
+ userMetaData = emptyMap();
+ userTransactionId = 0;
+ statistics.reset();
+ operations.release();
+ pool.release( this );
+ }
+ finally
+ {
+ terminationReleaseLock.unlock();
+ }
+ if ( auxStateCloseException != null )
+ {
+ throw auxStateCloseException;
+ }
+ }
+
+ private AuxiliaryTransactionStateCloseException closeAuxTxState()
+ {
+ AuxiliaryTransactionStateHolder holder = auxTxStateHolder;
+ auxTxStateHolder = null;
+ try
+ {
+ holder.close();
+ }
+ catch ( AuxiliaryTransactionStateCloseException e )
+ {
+ return e;
+ }
+ return null;
+ }
+
+ /**
+ * Transaction can be terminated only when it is not closed and not already terminated.
+ * Otherwise termination does not make sense.
+ */
+ private boolean canBeTerminated()
+ {
+ return !closed && !isTerminated();
+ }
+
+ @Override
+ public boolean isTerminated()
+ {
+ return terminationReason != null;
+ }
+
+ @Override
+ public long lastTransactionTimestampWhenStarted()
+ {
+ return lastTransactionTimestampWhenStarted;
+ }
+
+ @Override
+ public void registerCloseListener( CloseListener listener )
+ {
+ assert listener != null;
+ closeListeners.add( listener );
+ }
+
+ @Override
+ public Type transactionType()
+ {
+ return type;
+ }
+
+ @Override
+ public long getTransactionId()
+ {
+ if ( transactionId == NOT_COMMITTED_TRANSACTION_ID )
+ {
+ throw new IllegalStateException( "Transaction id is not assigned yet. " +
+ "It will be assigned during transaction commit." );
+ }
+ return transactionId;
+ }
+
+ @Override
+ public long getCommitTime()
+ {
+ if ( commitTime == NOT_COMMITTED_TRANSACTION_COMMIT_TIME )
+ {
+ throw new IllegalStateException( "Transaction commit time is not assigned yet. " +
+ "It will be assigned during transaction commit." );
+ }
+ return commitTime;
+ }
+
+ @Override
+ public Revertable overrideWith( SecurityContext context )
+ {
+ SecurityContext oldContext = this.securityContext;
+ this.securityContext = context;
+ return () -> this.securityContext = oldContext;
+ }
+
+ @Override
+ public String toString()
+ {
+ String lockSessionId = statementLocks == null
+ ? "statementLocks == null"
+ : String.valueOf( statementLocks.pessimistic().getLockSessionId() );
+
+ return "KernelTransaction[" + lockSessionId + "]";
+ }
+
+ public void dispose()
+ {
+ storageReader.close();
+ }
+
+ /**
+ * This method will be invoked by concurrent threads for inspecting the locks held by this transaction.
+ *
+ * The fact that {@link #statementLocks} is a volatile fields, grants us enough of a read barrier to get a good
+ * enough snapshot of the lock state (as long as the underlying methods give us such guarantees).
+ *
+ * @return the locks held by this transaction.
+ */
+ public Stream extends ActiveLock> activeLocks()
+ {
+ StatementLocks locks = this.statementLocks;
+ return locks == null ? Stream.empty() : locks.activeLocks();
+ }
+
+ long userTransactionId()
+ {
+ return userTransactionId;
+ }
+
+ public Statistics getStatistics()
+ {
+ return statistics;
+ }
+
+ private TxStateVisitor enforceConstraints( TxStateVisitor txStateVisitor )
+ {
+ return constraintSemantics.decorateTxStateVisitor( storageReader, operations.dataRead(), operations.cursors(), txState, txStateVisitor );
+ }
+
+ /**
+ * The revision of the data changes in this transaction. This number is opaque, except that it is zero if there have been no data changes in this
+ * transaction. And making and then undoing a change does not count as "no data changes." This number will always change when there is a data change in a
+ * transaction, however, such that one can reliably tell whether or not there has been any data changes in a transaction since last time the transaction
+ * data revision was obtained for the given transaction.
+ * @return The opaque data revision for this transaction, or zero if there has been no data changes in this transaction.
+ */
+ public long getTransactionDataRevision()
+ {
+ return hasDataChanges() ? txState.getDataRevision() : 0;
+ }
+
+ public static class Statistics
+ {
+ private volatile long cpuTimeNanosWhenQueryStarted;
+ private volatile long heapAllocatedBytesWhenQueryStarted;
+ private volatile long waitingTimeNanos;
+ private volatile long transactionThreadId;
+ private volatile PageCursorTracer pageCursorTracer = PageCursorTracer.NULL;
+ private final KernelTransactionImplementation transaction;
+ private final AtomicReference cpuClockRef;
+ private final AtomicReference heapAllocationRef;
+ private CpuClock cpuClock;
+ private HeapAllocation heapAllocation;
+
+ public Statistics( KernelTransactionImplementation transaction, AtomicReference cpuClockRef,
+ AtomicReference heapAllocationRef )
+ {
+ this.transaction = transaction;
+ this.cpuClockRef = cpuClockRef;
+ this.heapAllocationRef = heapAllocationRef;
+ }
+
+ protected void init( long threadId, PageCursorTracer pageCursorTracer )
+ {
+ this.cpuClock = cpuClockRef.get();
+ this.heapAllocation = heapAllocationRef.get();
+ this.transactionThreadId = threadId;
+ this.pageCursorTracer = pageCursorTracer;
+ this.cpuTimeNanosWhenQueryStarted = cpuClock.cpuTimeNanos( transactionThreadId );
+ this.heapAllocatedBytesWhenQueryStarted = heapAllocation.allocatedBytes( transactionThreadId );
+ }
+
+ /**
+ * Returns number of allocated bytes by current transaction.
+ * @return number of allocated bytes by the thread.
+ */
+ long heapAllocatedBytes()
+ {
+ return heapAllocation.allocatedBytes( transactionThreadId ) - heapAllocatedBytesWhenQueryStarted;
+ }
+
+ /**
+ * Returns amount of direct memory allocated by current transaction.
+ *
+ * @return amount of direct memory allocated by the thread in bytes.
+ */
+ long directAllocatedBytes()
+ {
+ return transaction.collectionsFactory.getMemoryTracker().usedDirectMemory();
+ }
+
+ /**
+ * Return CPU time used by current transaction in milliseconds
+ * @return the current CPU time used by the transaction, in milliseconds.
+ */
+ public long cpuTimeMillis()
+ {
+ long cpuTimeNanos = cpuClock.cpuTimeNanos( transactionThreadId ) - cpuTimeNanosWhenQueryStarted;
+ return NANOSECONDS.toMillis( cpuTimeNanos );
+ }
+
+ /**
+ * Return total number of page cache hits that current transaction performed
+ * @return total page cache hits
+ */
+ long totalTransactionPageCacheHits()
+ {
+ return pageCursorTracer.accumulatedHits();
+ }
+
+ /**
+ * Return total number of page cache faults that current transaction performed
+ * @return total page cache faults
+ */
+ long totalTransactionPageCacheFaults()
+ {
+ return pageCursorTracer.accumulatedFaults();
+ }
+
+ /**
+ * Report how long any particular query was waiting during it's execution
+ * @param waitTimeNanos query waiting time in nanoseconds
+ */
+ @SuppressWarnings( "NonAtomicOperationOnVolatileField" )
+ void addWaitingTime( long waitTimeNanos )
+ {
+ waitingTimeNanos += waitTimeNanos;
+ }
+
+ /**
+ * Accumulated transaction waiting time that includes waiting time of all already executed queries
+ * plus waiting time of currently executed query.
+ * @return accumulated transaction waiting time
+ * @param nowNanos current moment in nanoseconds
+ */
+ long getWaitingTimeNanos( long nowNanos )
+ {
+ ExecutingQueryList queryList = transaction.executingQueries();
+ long waitingTime = waitingTimeNanos;
+ if ( queryList != null )
+ {
+ Long latestQueryWaitingNanos = queryList.top( executingQuery ->
+ executingQuery.totalWaitingTimeNanos( nowNanos ) );
+ waitingTime = latestQueryWaitingNanos != null ? waitingTime + latestQueryWaitingNanos : waitingTime;
+ }
+ return waitingTime;
+ }
+
+ void reset()
+ {
+ pageCursorTracer = PageCursorTracer.NULL;
+ cpuTimeNanosWhenQueryStarted = 0;
+ heapAllocatedBytesWhenQueryStarted = 0;
+ waitingTimeNanos = 0;
+ transactionThreadId = -1;
+ }
+ }
+
+ @Override
+ public ClockContext clocks()
+ {
+ return clocks;
+ }
+
+ @Override
+ public NodeCursor ambientNodeCursor()
+ {
+ return operations.nodeCursor();
+ }
+
+ @Override
+ public RelationshipScanCursor ambientRelationshipCursor()
+ {
+ return operations.relationshipCursor();
+ }
+
+ @Override
+ public PropertyCursor ambientPropertyCursor()
+ {
+ return operations.propertyCursor();
+ }
+
+ /**
+ * It is not allowed for the same transaction to perform database writes as well as schema writes.
+ * This enum tracks the current write transactionStatus of the transaction, allowing it to transition from
+ * no writes (NONE) to data writes (DATA) or schema writes (SCHEMA), but it cannot transition between
+ * DATA and SCHEMA without throwing an InvalidTransactionTypeKernelException. Note that this behavior
+ * is orthogonal to the SecurityContext which manages what the transaction or statement is allowed to do
+ * based on authorization.
+ */
+ private enum TransactionWriteState
+ {
+ NONE,
+ DATA
+ {
+ @Override
+ TransactionWriteState upgradeToSchemaWrites() throws InvalidTransactionTypeKernelException
+ {
+ throw new InvalidTransactionTypeKernelException(
+ "Cannot perform schema updates in a transaction that has performed data updates." );
+ }
+ },
+ SCHEMA
+ {
+ @Override
+ TransactionWriteState upgradeToDataWrites() throws InvalidTransactionTypeKernelException
+ {
+ throw new InvalidTransactionTypeKernelException(
+ "Cannot perform data updates in a transaction that has performed schema updates." );
+ }
+ };
+
+ TransactionWriteState upgradeToDataWrites() throws InvalidTransactionTypeKernelException
+ {
+ return DATA;
+ }
+
+ TransactionWriteState upgradeToSchemaWrites() throws InvalidTransactionTypeKernelException
+ {
+ return SCHEMA;
+ }
+ }
+}
diff --git a/external-properties/src/main/java/org/neo4j/kernel/impl/newapi/Operations.java b/external-properties/src/main/java/org/neo4j/kernel/impl/newapi/Operations.java
new file mode 100644
index 00000000..f350b841
--- /dev/null
+++ b/external-properties/src/main/java/org/neo4j/kernel/impl/newapi/Operations.java
@@ -0,0 +1,1612 @@
+/*
+ * Copyright (c) 2002-2019 "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.impl.newapi;
+
+import cn.pandadb.externalprops.ExternalPropertiesContext;
+import cn.pandadb.util.GlobalContext;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.mutable.MutableInt;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Optional;
+
+import org.neo4j.graphdb.factory.GraphDatabaseSettings;
+import org.neo4j.internal.kernel.api.CursorFactory;
+import org.neo4j.internal.kernel.api.ExplicitIndexRead;
+import org.neo4j.internal.kernel.api.ExplicitIndexWrite;
+import org.neo4j.internal.kernel.api.IndexQuery;
+import org.neo4j.internal.kernel.api.IndexReference;
+import org.neo4j.internal.kernel.api.Locks;
+import org.neo4j.internal.kernel.api.NodeLabelIndexCursor;
+import org.neo4j.internal.kernel.api.Procedures;
+import org.neo4j.internal.kernel.api.Read;
+import org.neo4j.internal.kernel.api.SchemaRead;
+import org.neo4j.internal.kernel.api.SchemaWrite;
+import org.neo4j.internal.kernel.api.Token;
+import org.neo4j.internal.kernel.api.Write;
+import org.neo4j.internal.kernel.api.exceptions.EntityNotFoundException;
+import org.neo4j.internal.kernel.api.exceptions.KernelException;
+import org.neo4j.internal.kernel.api.exceptions.TransactionFailureException;
+import org.neo4j.internal.kernel.api.exceptions.explicitindex.AutoIndexingKernelException;
+import org.neo4j.internal.kernel.api.exceptions.explicitindex.ExplicitIndexNotFoundKernelException;
+import org.neo4j.internal.kernel.api.exceptions.schema.ConstraintValidationException;
+import org.neo4j.internal.kernel.api.exceptions.schema.CreateConstraintFailureException;
+import org.neo4j.internal.kernel.api.exceptions.schema.IndexNotApplicableKernelException;
+import org.neo4j.internal.kernel.api.exceptions.schema.IndexNotFoundKernelException;
+import org.neo4j.internal.kernel.api.exceptions.schema.SchemaKernelException;
+import org.neo4j.internal.kernel.api.schema.IndexProviderDescriptor;
+import org.neo4j.internal.kernel.api.schema.LabelSchemaDescriptor;
+import org.neo4j.internal.kernel.api.schema.RelationTypeSchemaDescriptor;
+import org.neo4j.internal.kernel.api.schema.SchemaDescriptor;
+import org.neo4j.internal.kernel.api.schema.constraints.ConstraintDescriptor;
+import org.neo4j.kernel.api.SilentTokenNameLookup;
+import org.neo4j.kernel.api.StatementConstants;
+import org.neo4j.kernel.api.exceptions.index.IndexEntryConflictException;
+import org.neo4j.kernel.api.exceptions.schema.AlreadyConstrainedException;
+import org.neo4j.kernel.api.exceptions.schema.AlreadyIndexedException;
+import org.neo4j.kernel.api.exceptions.schema.DropConstraintFailureException;
+import org.neo4j.kernel.api.exceptions.schema.DropIndexFailureException;
+import org.neo4j.kernel.api.exceptions.schema.IndexBelongsToConstraintException;
+import org.neo4j.kernel.api.exceptions.schema.IndexBrokenKernelException;
+import org.neo4j.kernel.api.exceptions.schema.NoSuchConstraintException;
+import org.neo4j.kernel.api.exceptions.schema.NoSuchIndexException;
+import org.neo4j.kernel.api.exceptions.schema.RepeatedPropertyInCompositeSchemaException;
+import org.neo4j.kernel.api.exceptions.schema.UnableToValidateConstraintException;
+import org.neo4j.kernel.api.exceptions.schema.UniquePropertyValueValidationException;
+import org.neo4j.kernel.api.explicitindex.AutoIndexing;
+import org.neo4j.kernel.api.schema.constraints.ConstraintDescriptorFactory;
+import org.neo4j.kernel.api.schema.constraints.IndexBackedConstraintDescriptor;
+import org.neo4j.kernel.api.schema.constraints.NodeKeyConstraintDescriptor;
+import org.neo4j.kernel.api.schema.constraints.UniquenessConstraintDescriptor;
+import org.neo4j.kernel.api.txstate.ExplicitIndexTransactionState;
+import org.neo4j.kernel.api.txstate.TransactionState;
+import org.neo4j.kernel.configuration.Config;
+import org.neo4j.kernel.impl.api.KernelTransactionImplementation;
+import org.neo4j.kernel.impl.api.index.IndexingService;
+import org.neo4j.kernel.impl.api.state.ConstraintIndexCreator;
+import org.neo4j.kernel.impl.constraints.ConstraintSemantics;
+import org.neo4j.kernel.impl.index.IndexEntityType;
+import org.neo4j.kernel.impl.locking.ResourceTypes;
+import org.neo4j.storageengine.api.EntityType;
+import org.neo4j.storageengine.api.StorageReader;
+import org.neo4j.storageengine.api.lock.ResourceType;
+import org.neo4j.storageengine.api.schema.IndexDescriptor;
+import org.neo4j.storageengine.api.schema.IndexDescriptorFactory;
+import org.neo4j.values.storable.Value;
+import org.neo4j.values.storable.Values;
+
+import static java.lang.Math.max;
+import static java.lang.Math.min;
+import static org.neo4j.internal.kernel.api.exceptions.schema.ConstraintValidationException.Phase.VALIDATION;
+import static org.neo4j.internal.kernel.api.exceptions.schema.SchemaKernelException.OperationContext.CONSTRAINT_CREATION;
+import static org.neo4j.internal.kernel.api.schema.SchemaDescriptor.schemaTokenLockingIds;
+import static org.neo4j.kernel.api.StatementConstants.NO_SUCH_LABEL;
+import static org.neo4j.kernel.api.StatementConstants.NO_SUCH_NODE;
+import static org.neo4j.kernel.api.StatementConstants.NO_SUCH_PROPERTY_KEY;
+import static org.neo4j.kernel.impl.locking.ResourceTypes.INDEX_ENTRY;
+import static org.neo4j.kernel.impl.locking.ResourceTypes.indexEntryResourceId;
+import static org.neo4j.kernel.impl.newapi.IndexTxStateUpdater.LabelChangeType.ADDED_LABEL;
+import static org.neo4j.kernel.impl.newapi.IndexTxStateUpdater.LabelChangeType.REMOVED_LABEL;
+import static org.neo4j.storageengine.api.EntityType.NODE;
+import static org.neo4j.storageengine.api.schema.IndexDescriptor.Type.UNIQUE;
+import static org.neo4j.values.storable.Values.NO_VALUE;
+
+// NOTE: pandadb
+import org.neo4j.internal.kernel.api.exceptions.LabelNotFoundKernelException;
+import org.neo4j.internal.kernel.api.exceptions.PropertyKeyIdNotFoundKernelException;
+import cn.pandadb.externalprops.CustomPropertyNodeStore;
+import cn.pandadb.externalprops.PropertyWriteTransaction;
+import cn.pandadb.externalprops.NodeWithProperties;
+import org.neo4j.values.virtual.NodeValue;
+import scala.Option;
+import scala.collection.mutable.Undoable;
+// END-NOTE
+
+/**
+ * Collects all Kernel API operations and guards them from being used outside of transaction.
+ *
+ * Many methods assume cursors to be initialized before use in private methods, even if they're not passed in explicitly.
+ * Keep that in mind: e.g. nodeCursor, propertyCursor and relationshipCursor
+ */
+public class Operations implements Write, ExplicitIndexWrite, SchemaWrite
+{
+ private static final int[] EMPTY_INT_ARRAY = new int[0];
+
+ private final KernelTransactionImplementation ktx;
+ private final AllStoreHolder allStoreHolder;
+ private final KernelToken token;
+ private final StorageReader statement;
+ private final AutoIndexing autoIndexing;
+ private final IndexTxStateUpdater updater;
+ private final DefaultCursors cursors;
+ private final ConstraintIndexCreator constraintIndexCreator;
+ private final ConstraintSemantics constraintSemantics;
+ private final IndexingService indexingService;
+ private final Config config;
+ private DefaultNodeCursor nodeCursor;
+ private DefaultPropertyCursor propertyCursor;
+ private DefaultRelationshipScanCursor relationshipCursor;
+
+ // NOTE: pandadb
+ private CustomPropertyWriteTransactionFacade customPropWriteTx;
+ // END-NOTE
+
+ public Operations( AllStoreHolder allStoreHolder, IndexTxStateUpdater updater, StorageReader statement, KernelTransactionImplementation ktx,
+ KernelToken token, DefaultCursors cursors, AutoIndexing autoIndexing, ConstraintIndexCreator constraintIndexCreator,
+ ConstraintSemantics constraintSemantics, IndexingService indexingService, Config config )
+ {
+ this.token = token;
+ this.autoIndexing = autoIndexing;
+ this.allStoreHolder = allStoreHolder;
+ this.ktx = ktx;
+ this.statement = statement;
+ this.updater = updater;
+ this.cursors = cursors;
+ this.constraintIndexCreator = constraintIndexCreator;
+ this.constraintSemantics = constraintSemantics;
+ this.indexingService = indexingService;
+ this.config = config;
+ // NOTE: pandadb
+ this.customPropWriteTx = new CustomPropertyWriteTransactionFacade();
+ // END-NOTE
+ }
+
+ public void initialize()
+ {
+ this.nodeCursor = cursors.allocateNodeCursor();
+ this.propertyCursor = cursors.allocatePropertyCursor();
+ this.relationshipCursor = cursors.allocateRelationshipScanCursor();
+
+ // NOTE: pandadb
+ this.customPropWriteTx = new CustomPropertyWriteTransactionFacade();
+ // END-NOTE
+ }
+
+ // NOTE: pandadb
+ public CustomPropertyWriteTransactionFacade customPropWriteTx()
+ {
+ return this.customPropWriteTx;
+ }
+
+ public class CustomPropertyWriteTransactionFacade
+ {
+ private Option customPropertyStore;
+ private PropertyWriteTransaction customPropWrTx;
+ private Undoable commitedTxRes;
+
+ public CustomPropertyWriteTransactionFacade()
+ {
+ this.customPropertyStore = ExternalPropertiesContext.maybeCustomPropertyNodeStore();
+ if (this.isLeaderNode() && this.customPropertyStore.isDefined())
+ {
+ this.customPropWrTx = this.customPropertyStore.get().beginWriteTransaction();
+ }
+
+ }
+
+ private boolean isLeaderNode()
+ {
+ return GlobalContext.isLeaderNode();
+ }
+
+ private String getNodeLabelName(int label)
+ {
+ try
+ {
+ return token().nodeLabelName(label);
+ }
+ catch ( LabelNotFoundKernelException e )
+ {
+ throw new IllegalStateException( "Label retrieved through kernel API should exist.", e );
+ }
+ }
+
+ private String getPropertyKeyName(int property)
+ {
+ try
+ {
+ return token().propertyKeyName(property);
+ }
+ catch ( PropertyKeyIdNotFoundKernelException e )
+ {
+ throw new IllegalStateException( "Property key retrieved through kernel API should exist.", e );
+ }
+ }
+
+ private boolean isSavePropertyToCustom()
+ {
+ return this.isLeaderNode() && this.customPropWrTx != null;
+ }
+
+ public boolean isPreventNeo4jPropStore()
+ {
+ return this.customPropertyStore.isDefined();
+ }
+
+ public void nodeCreate(long nodeId)
+ {
+ if (this.isSavePropertyToCustom()) {
+ this.customPropWrTx.addNode(nodeId);
+ }
+ }
+
+ public Value nodeGetProperty(long nodeId, int propertyKeyId)
+ {
+ return this.nodeGetProperty(nodeId, getPropertyKeyName(propertyKeyId));
+ }
+
+ public Value nodeGetProperty(long nodeId, String propertyKey)
+ {
+ if (this.isPreventNeo4jPropStore()) {
+ Option rs = this.customPropWrTx.getPropertyValue(nodeId, propertyKey);
+ if (rs.isDefined()) {
+ return rs.get();
+ }
+ }
+ return NO_VALUE;
+ }
+
+ /*
+ public NodeValue getNode(long nodeId)
+ {
+ if (this.isPreventNeo4jPropStore()) {
+ NodeWithProperties node = this.customPropertyStore.get().getNodeById(nodeId).get();
+ return node.toNeo4jNodeValue();
+ }
+ return null;
+ }
+ */
+
+ public void nodeDelete(long nodeId)
+ {
+ if (this.isSavePropertyToCustom()) {
+ this.customPropWrTx.deleteNode(nodeId);
+ }
+ }
+
+ public void nodeCreateWithLabelNames(long nodeId, Iterable labels)
+ {
+ if (this.isSavePropertyToCustom()) {
+ this.customPropWrTx.addNode(nodeId);
+ for (String label: labels){
+ this.customPropWrTx.addLabel(nodeId,label);
+ }
+ }
+ }
+
+ public void nodeCreateWithLabels(long nodeId, Iterable labels)
+ {
+ if (this.isSavePropertyToCustom()) {
+ this.customPropWrTx.addNode(nodeId);
+ for (int labelId : labels) {
+ this.customPropWrTx.addLabel(nodeId, getNodeLabelName(labelId));
+ }
+ }
+ }
+
+ public void nodeSetLabel(long nodeId, int label)
+ {
+ if (this.isSavePropertyToCustom()) {
+ this.customPropWrTx.addLabel(nodeId, getNodeLabelName(label));
+ }
+ }
+
+ public void nodeRemoveLabel(long nodeId, int label)
+ {
+ if (this.isSavePropertyToCustom()) {
+ this.customPropWrTx.removeLabel(nodeId, getNodeLabelName(label));
+ }
+ }
+
+ public void nodeSetProperty(long nodeId, int property, Value value)
+ {
+ if (this.isSavePropertyToCustom()) {
+ this.customPropWrTx.addProperty(nodeId, getPropertyKeyName(property), value);
+ }
+ }
+
+ public void nodeRemoveProperty(long nodeId, int property)
+ {
+ if (this.isSavePropertyToCustom()) {
+ this.customPropWrTx.removeProperty(nodeId, getPropertyKeyName(property));
+ }
+ }
+
+ public void commit()
+ {
+ if (this.isSavePropertyToCustom()) {
+ this.commitedTxRes = this.customPropWrTx.commit();
+ }
+ }
+
+ public void rollback()
+ {
+ if (this.customPropWrTx != null) {
+ this.customPropWrTx.rollback();
+ }
+ }
+
+ public void undo()
+ {
+ if (this.isSavePropertyToCustom()) {
+ if (this.commitedTxRes != null) {
+ this.commitedTxRes.undo();
+ }
+ }
+ }
+
+ public void close()
+ {
+ if (this.customPropWrTx != null) {
+ this.customPropWrTx.close();
+ }
+ }
+ }
+ // END-NOTE
+
+ @Override
+ public long nodeCreate()
+ {
+ ktx.assertOpen();
+ long nodeId = statement.reserveNode();
+ ktx.txState().nodeDoCreate( nodeId );
+ // NOTE: pandadb
+ this.customPropWriteTx.nodeCreate(nodeId);
+ // END-NOTE
+ return nodeId;
+ }
+
+ @Override
+ public long nodeCreateWithLabels( int[] labels ) throws ConstraintValidationException
+ {
+ if ( labels == null || labels.length == 0 )
+ {
+ return nodeCreate();
+ }
+
+ // We don't need to check the node for existence, like we do in nodeAddLabel, because we just created it.
+ // We also don't need to check if the node already has some of the labels, because we know it has none.
+ // And we don't need to take the exclusive lock on the node, because it was created in this transaction and
+ // isn't visible to anyone else yet.
+ ktx.assertOpen();
+ long[] lockingIds = SchemaDescriptor.schemaTokenLockingIds( labels );
+ Arrays.sort( lockingIds ); // Sort to ensure labels are locked and assigned in order.
+ ktx.statementLocks().optimistic().acquireShared( ktx.lockTracer(), ResourceTypes.LABEL, lockingIds );
+ long nodeId = statement.reserveNode();
+ ktx.txState().nodeDoCreate( nodeId );
+ // NOTE: pandadb
+ this.customPropWriteTx.nodeCreate(nodeId);
+ // END-NOTE
+ nodeCursor.single( nodeId, allStoreHolder );
+ nodeCursor.next();
+
+ int prevLabel = NO_SUCH_LABEL;
+ for ( long lockingId : lockingIds )
+ {
+ int label = (int) lockingId;
+ if ( label != prevLabel ) // Filter out duplicates.
+ {
+ checkConstraintsAndAddLabelToNode( nodeId, label );
+ prevLabel = label;
+ }
+ }
+ return nodeId;
+ }
+
+ @Override
+ public boolean nodeDelete( long node ) throws AutoIndexingKernelException
+ {
+ ktx.assertOpen();
+ return nodeDelete( node, true );
+ }
+
+ @Override
+ public int nodeDetachDelete( final long nodeId ) throws KernelException
+ {
+ final MutableInt count = new MutableInt();
+ TwoPhaseNodeForRelationshipLocking locking = new TwoPhaseNodeForRelationshipLocking(
+ relId ->
+ {
+ ktx.assertOpen();
+ if ( relationshipDelete( relId, false ) )
+ {
+ count.increment();
+ }
+ }, ktx.statementLocks().optimistic(), ktx.lockTracer() );
+
+ locking.lockAllNodesAndConsumeRelationships( nodeId, ktx, ktx.ambientNodeCursor() );
+ ktx.assertOpen();
+
+ //we are already holding the lock
+ nodeDelete( nodeId, false );
+ return count.intValue();
+ }
+
+ @Override
+ public long relationshipCreate( long sourceNode, int relationshipType, long targetNode )
+ throws EntityNotFoundException
+ {
+ ktx.assertOpen();
+
+ sharedSchemaLock( ResourceTypes.RELATIONSHIP_TYPE, relationshipType );
+ lockRelationshipNodes( sourceNode, targetNode );
+
+ assertNodeExists( sourceNode );
+ assertNodeExists( targetNode );
+
+ long id = statement.reserveRelationship();
+ ktx.txState().relationshipDoCreate( id, relationshipType, sourceNode, targetNode );
+ return id;
+ }
+
+ @Override
+ public boolean relationshipDelete( long relationship ) throws AutoIndexingKernelException
+ {
+ ktx.assertOpen();
+ return relationshipDelete( relationship, true );
+ }
+
+ @Override
+ public boolean nodeAddLabel( long node, int nodeLabel )
+ throws EntityNotFoundException, ConstraintValidationException
+ {
+ sharedSchemaLock( ResourceTypes.LABEL, nodeLabel );
+ acquireExclusiveNodeLock( node );
+
+ ktx.assertOpen();
+ singleNode( node );
+
+ if ( nodeCursor.hasLabel( nodeLabel ) )
+ {
+ //label already there, nothing to do
+ return false;
+ }
+
+ checkConstraintsAndAddLabelToNode( node, nodeLabel );
+ return true;
+ }
+
+ private void checkConstraintsAndAddLabelToNode( long node, int nodeLabel )
+ throws UniquePropertyValueValidationException, UnableToValidateConstraintException
+ {
+ // Load the property key id list for this node. We may need it for constraint validation if there are any related constraints,
+ // but regardless we need it for tx state updating
+ int[] existingPropertyKeyIds = loadSortedPropertyKeyList();
+
+ //Check so that we are not breaking uniqueness constraints
+ //We do this by checking if there is an existing node in the index that
+ //with the same label and property combination.
+ if ( existingPropertyKeyIds.length > 0 )
+ {
+ for ( IndexBackedConstraintDescriptor uniquenessConstraint : indexingService.getRelatedUniquenessConstraints( new long[]{nodeLabel},
+ existingPropertyKeyIds, NODE ) )
+ {
+ IndexQuery.ExactPredicate[] propertyValues = getAllPropertyValues( uniquenessConstraint.schema(),
+ StatementConstants.NO_SUCH_PROPERTY_KEY, Values.NO_VALUE );
+ if ( propertyValues != null )
+ {
+ validateNoExistingNodeWithExactValues( uniquenessConstraint, propertyValues, node );
+ }
+ }
+ }
+
+ // NOTE: pandadb
+ this.customPropWriteTx.nodeSetLabel(node, nodeLabel);
+ // END-NOTE
+ //node is there and doesn't already have the label, let's add
+ ktx.txState().nodeDoAddLabel( nodeLabel, node );
+ updater.onLabelChange( nodeLabel, existingPropertyKeyIds, nodeCursor, propertyCursor, ADDED_LABEL );
+ }
+
+ private int[] loadSortedPropertyKeyList()
+ {
+ nodeCursor.properties( propertyCursor );
+ if ( !propertyCursor.next() )
+ {
+ return EMPTY_INT_ARRAY;
+ }
+
+ int[] propertyKeyIds = new int[4]; // just some arbitrary starting point, it grows on demand
+ int cursor = 0;
+ do
+ {
+ if ( cursor == propertyKeyIds.length )
+ {
+ propertyKeyIds = Arrays.copyOf( propertyKeyIds, cursor * 2 );
+ }
+ propertyKeyIds[cursor++] = propertyCursor.propertyKey();
+ }
+ while ( propertyCursor.next() );
+ if ( cursor != propertyKeyIds.length )
+ {
+ propertyKeyIds = Arrays.copyOf( propertyKeyIds, cursor );
+ }
+ Arrays.sort( propertyKeyIds );
+ return propertyKeyIds;
+ }
+
+ private boolean nodeDelete( long node, boolean lock ) throws AutoIndexingKernelException
+ {
+ ktx.assertOpen();
+
+ if ( ktx.hasTxStateWithChanges() )
+ {
+ if ( ktx.txState().nodeIsAddedInThisTx( node ) )
+ {
+ autoIndexing.nodes().entityRemoved( this, node );
+ // NOTE: pandadb
+ this.customPropWriteTx.nodeDelete(node);
+ // END-NOTE
+ ktx.txState().nodeDoDelete( node );
+ return true;
+ }
+ if ( ktx.txState().nodeIsDeletedInThisTx( node ) )
+ {
+ // already deleted
+ return false;
+ }
+ }
+
+ if ( lock )
+ {
+ ktx.statementLocks().optimistic().acquireExclusive( ktx.lockTracer(), ResourceTypes.NODE, node );
+ }
+
+ allStoreHolder.singleNode( node, nodeCursor );
+ if ( nodeCursor.next() )
+ {
+ acquireSharedNodeLabelLocks();
+
+ autoIndexing.nodes().entityRemoved( this, node );
+ // NOTE: pandadb
+ this.customPropWriteTx.nodeDelete(node);
+ // END-NOTE
+ ktx.txState().nodeDoDelete( node );
+ return true;
+ }
+
+ // tried to delete node that does not exist
+ return false;
+ }
+
+ /**
+ * Assuming that the nodeCursor have been initialized to the node that labels are retrieved from
+ */
+ private long[] acquireSharedNodeLabelLocks()
+ {
+ long[] labels = nodeCursor.labels().all();
+ ktx.statementLocks().optimistic().acquireShared( ktx.lockTracer(), ResourceTypes.LABEL, labels );
+ return labels;
+ }
+
+ private boolean relationshipDelete( long relationship, boolean lock ) throws AutoIndexingKernelException
+ {
+ allStoreHolder.singleRelationship( relationship, relationshipCursor ); // tx-state aware
+
+ if ( relationshipCursor.next() )
+ {
+ if ( lock )
+ {
+ lockRelationshipNodes( relationshipCursor.sourceNodeReference(),
+ relationshipCursor.targetNodeReference() );
+ acquireExclusiveRelationshipLock( relationship );
+ }
+ if ( !allStoreHolder.relationshipExists( relationship ) )
+ {
+ return false;
+ }
+
+ ktx.assertOpen();
+
+ autoIndexing.relationships().entityRemoved( this, relationship );
+
+ TransactionState txState = ktx.txState();
+ if ( txState.relationshipIsAddedInThisTx( relationship ) )
+ {
+ txState.relationshipDoDeleteAddedInThisTx( relationship );
+ }
+ else
+ {
+ txState.relationshipDoDelete( relationship, relationshipCursor.type(),
+ relationshipCursor.sourceNodeReference(), relationshipCursor.targetNodeReference() );
+ }
+ return true;
+ }
+
+ // tried to delete relationship that does not exist
+ return false;
+ }
+
+ private void singleNode( long node ) throws EntityNotFoundException
+ {
+ allStoreHolder.singleNode( node, nodeCursor );
+ if ( !nodeCursor.next() )
+ {
+ throw new EntityNotFoundException( NODE, node );
+ }
+ }
+
+ private void singleRelationship( long relationship ) throws EntityNotFoundException
+ {
+ allStoreHolder.singleRelationship( relationship, relationshipCursor );
+ if ( !relationshipCursor.next() )
+ {
+ throw new EntityNotFoundException( EntityType.RELATIONSHIP, relationship );
+ }
+ }
+
+ /**
+ * Fetch the property values for all properties in schema for a given node. Return these as an exact predicate
+ * array.
+ */
+ private IndexQuery.ExactPredicate[] getAllPropertyValues( SchemaDescriptor schema, int changedPropertyKeyId,
+ Value changedValue )
+ {
+ int[] schemaPropertyIds = schema.getPropertyIds();
+ IndexQuery.ExactPredicate[] values = new IndexQuery.ExactPredicate[schemaPropertyIds.length];
+
+ int nMatched = 0;
+ nodeCursor.properties( propertyCursor );
+ while ( propertyCursor.next() )
+ {
+ int nodePropertyId = propertyCursor.propertyKey();
+ int k = ArrayUtils.indexOf( schemaPropertyIds, nodePropertyId );
+ if ( k >= 0 )
+ {
+ if ( nodePropertyId != StatementConstants.NO_SUCH_PROPERTY_KEY )
+ {
+ values[k] = IndexQuery.exact( nodePropertyId, propertyCursor.propertyValue() );
+ }
+ nMatched++;
+ }
+ }
+
+ //This is true if we are adding a property
+ if ( changedPropertyKeyId != NO_SUCH_PROPERTY_KEY )
+ {
+ int k = ArrayUtils.indexOf( schemaPropertyIds, changedPropertyKeyId );
+ if ( k >= 0 )
+ {
+ values[k] = IndexQuery.exact( changedPropertyKeyId, changedValue );
+ nMatched++;
+ }
+ }
+
+ if ( nMatched < values.length )
+ {
+ return null;
+ }
+ return values;
+ }
+
+ /**
+ * Check so that there is not an existing node with the exact match of label and property
+ */
+ private void validateNoExistingNodeWithExactValues( IndexBackedConstraintDescriptor constraint,
+ IndexQuery.ExactPredicate[] propertyValues, long modifiedNode
+ ) throws UniquePropertyValueValidationException, UnableToValidateConstraintException
+ {
+ IndexDescriptor schemaIndexDescriptor = constraint.ownedIndexDescriptor();
+ IndexReference indexReference = allStoreHolder.indexGetCapability( schemaIndexDescriptor );
+ try ( DefaultNodeValueIndexCursor valueCursor = cursors.allocateNodeValueIndexCursor();
+ IndexReaders indexReaders = new IndexReaders( indexReference, allStoreHolder ) )
+ {
+ assertIndexOnline( schemaIndexDescriptor );
+ int labelId = schemaIndexDescriptor.schema().keyId();
+
+ //Take a big fat lock, and check for existing node in index
+ ktx.statementLocks().optimistic().acquireExclusive(
+ ktx.lockTracer(), INDEX_ENTRY,
+ indexEntryResourceId( labelId, propertyValues )
+ );
+
+ allStoreHolder.nodeIndexSeekWithFreshIndexReader( valueCursor, indexReaders.createReader(), propertyValues );
+ if ( valueCursor.next() && valueCursor.nodeReference() != modifiedNode )
+ {
+ throw new UniquePropertyValueValidationException( constraint, VALIDATION,
+ new IndexEntryConflictException( valueCursor.nodeReference(), NO_SUCH_NODE,
+ IndexQuery.asValueTuple( propertyValues ) ) );
+ }
+ }
+ catch ( IndexNotFoundKernelException | IndexBrokenKernelException | IndexNotApplicableKernelException e )
+ {
+ throw new UnableToValidateConstraintException( constraint, e );
+ }
+ }
+
+ private void assertIndexOnline( IndexDescriptor descriptor )
+ throws IndexNotFoundKernelException, IndexBrokenKernelException
+ {
+ switch ( allStoreHolder.indexGetState( descriptor ) )
+ {
+ case ONLINE:
+ return;
+ default:
+ throw new IndexBrokenKernelException( allStoreHolder.indexGetFailure( descriptor ) );
+ }
+ }
+
+ @Override
+ public boolean nodeRemoveLabel( long node, int labelId ) throws EntityNotFoundException
+ {
+ acquireExclusiveNodeLock( node );
+ ktx.assertOpen();
+
+ singleNode( node );
+
+ if ( !nodeCursor.hasLabel( labelId ) )
+ {
+ //the label wasn't there, nothing to do
+ return false;
+ }
+
+ sharedSchemaLock( ResourceTypes.LABEL, labelId );
+
+ // NOTE: pandadb
+ this.customPropWriteTx.nodeRemoveLabel(node, labelId);
+ // END-NOTE
+
+ ktx.txState().nodeDoRemoveLabel( labelId, node );
+ if ( indexingService.hasRelatedSchema( labelId, NODE ) )
+ {
+ updater.onLabelChange( labelId, loadSortedPropertyKeyList(), nodeCursor, propertyCursor, REMOVED_LABEL );
+ }
+ return true;
+ }
+
+ @Override
+ public Value nodeSetProperty( long node, int propertyKey, Value value )
+ throws EntityNotFoundException, ConstraintValidationException, AutoIndexingKernelException
+ {
+ acquireExclusiveNodeLock( node );
+ ktx.assertOpen();
+
+ singleNode( node );
+ long[] labels = acquireSharedNodeLabelLocks();
+ Value existingValue = readNodeProperty( propertyKey );
+ int[] existingPropertyKeyIds = null;
+ boolean hasRelatedSchema = indexingService.hasRelatedSchema( labels, propertyKey, NODE );
+ if ( hasRelatedSchema )
+ {
+ existingPropertyKeyIds = loadSortedPropertyKeyList();
+ }
+
+ if ( hasRelatedSchema && !existingValue.equals( value ) )
+ {
+ // The value changed and there may be relevant constraints to check so let's check those now.
+ Collection uniquenessConstraints = indexingService.getRelatedUniquenessConstraints( labels, propertyKey, NODE );
+ NodeSchemaMatcher.onMatchingSchema( uniquenessConstraints.iterator(), propertyKey, existingPropertyKeyIds,
+ uniquenessConstraint ->
+ {
+ validateNoExistingNodeWithExactValues( uniquenessConstraint, getAllPropertyValues( uniquenessConstraint.schema(), propertyKey, value ),
+ node );
+ });
+ }
+
+ if ( existingValue == NO_VALUE )
+ {
+ //no existing value, we just add it
+ autoIndexing.nodes().propertyAdded( this, node, propertyKey, value );
+
+ // NOTE: pandadb
+ this.customPropWriteTx.nodeSetProperty(node, propertyKey, value);
+ //if(!this.customPropWriteTx.isPreventNeo4jPropStore())
+ //{
+ // ktx.txState().nodeDoAddProperty( node, propertyKey, value );
+ // }
+ // END-NOTE
+ ktx.txState().nodeDoAddProperty( node, propertyKey, value );
+
+ if ( hasRelatedSchema )
+ {
+ updater.onPropertyAdd( nodeCursor, propertyCursor, labels, propertyKey, existingPropertyKeyIds, value );
+ }
+ return NO_VALUE;
+ }
+ else
+ {
+ // We need to auto-index even if not actually changing the value.
+ autoIndexing.nodes().propertyChanged( this, node, propertyKey, existingValue, value );
+ if ( propertyHasChanged( value, existingValue ) )
+ {
+ //the value has changed to a new value
+
+ // NOTE: pandadb
+ this.customPropWriteTx.nodeSetProperty(node, propertyKey, value);
+ // if(!this.customPropWriteTx.isPreventNeo4jPropStore())
+ // {
+ // ktx.txState().nodeDoAddProperty( node, propertyKey, value );
+ // }
+ // END-NOTE
+ ktx.txState().nodeDoChangeProperty( node, propertyKey, value );
+
+ if ( hasRelatedSchema )
+ {
+ updater.onPropertyChange( nodeCursor, propertyCursor, labels, propertyKey, existingPropertyKeyIds, existingValue, value );
+ }
+ }
+ return existingValue;
+ }
+ }
+
+ @Override
+ public Value nodeRemoveProperty( long node, int propertyKey )
+ throws EntityNotFoundException, AutoIndexingKernelException
+ {
+ acquireExclusiveNodeLock( node );
+ ktx.assertOpen();
+ singleNode( node );
+
+ // NOTE: pandadb
+ this.customPropWriteTx.nodeRemoveProperty(node, propertyKey);
+ // END-NOTE
+
+ Value existingValue = readNodeProperty( propertyKey );
+
+ if ( existingValue != NO_VALUE )
+ {
+ long[] labels = acquireSharedNodeLabelLocks();
+ autoIndexing.nodes().propertyRemoved( this, node, propertyKey );
+ ktx.txState().nodeDoRemoveProperty( node, propertyKey );
+ if ( indexingService.hasRelatedSchema( labels, propertyKey, NODE ) )
+ {
+ updater.onPropertyRemove( nodeCursor, propertyCursor, labels, propertyKey, loadSortedPropertyKeyList(), existingValue );
+ }
+ }
+
+ return existingValue;
+ }
+
+ @Override
+ public Value relationshipSetProperty( long relationship, int propertyKey, Value value )
+ throws EntityNotFoundException, AutoIndexingKernelException
+ {
+ acquireExclusiveRelationshipLock( relationship );
+ ktx.assertOpen();
+ singleRelationship( relationship );
+ Value existingValue = readRelationshipProperty( propertyKey );
+ if ( existingValue == NO_VALUE )
+ {
+ autoIndexing.relationships().propertyAdded( this, relationship, propertyKey, value );
+ ktx.txState().relationshipDoReplaceProperty( relationship, propertyKey, NO_VALUE, value );
+ return NO_VALUE;
+ }
+ else
+ {
+ // We need to auto-index even if not actually changing the value.
+ autoIndexing.relationships().propertyChanged( this, relationship, propertyKey, existingValue, value );
+ if ( propertyHasChanged( existingValue, value ) )
+ {
+
+ ktx.txState().relationshipDoReplaceProperty( relationship, propertyKey, existingValue, value );
+ }
+
+ return existingValue;
+ }
+ }
+
+ @Override
+ public Value relationshipRemoveProperty( long relationship, int propertyKey )
+ throws EntityNotFoundException, AutoIndexingKernelException
+ {
+ acquireExclusiveRelationshipLock( relationship );
+ ktx.assertOpen();
+ singleRelationship( relationship );
+ Value existingValue = readRelationshipProperty( propertyKey );
+
+ if ( existingValue != NO_VALUE )
+ {
+ autoIndexing.relationships().propertyRemoved( this, relationship, propertyKey );
+ ktx.txState().relationshipDoRemoveProperty( relationship, propertyKey );
+ }
+
+ return existingValue;
+ }
+
+ @Override
+ public Value graphSetProperty( int propertyKey, Value value )
+ {
+ ktx.statementLocks().optimistic()
+ .acquireExclusive( ktx.lockTracer(), ResourceTypes.GRAPH_PROPS, ResourceTypes.graphPropertyResource() );
+ ktx.assertOpen();
+
+ Value existingValue = readGraphProperty( propertyKey );
+ if ( !existingValue.equals( value ) )
+ {
+ ktx.txState().graphDoReplaceProperty( propertyKey, existingValue, value );
+ }
+ return existingValue;
+ }
+
+ @Override
+ public Value graphRemoveProperty( int propertyKey )
+ {
+ ktx.statementLocks().optimistic()
+ .acquireExclusive( ktx.lockTracer(), ResourceTypes.GRAPH_PROPS, ResourceTypes.graphPropertyResource() );
+ ktx.assertOpen();
+ Value existingValue = readGraphProperty( propertyKey );
+ if ( existingValue != Values.NO_VALUE )
+ {
+ ktx.txState().graphDoRemoveProperty( propertyKey );
+ }
+ return existingValue;
+ }
+
+ @Override
+ public void nodeAddToExplicitIndex( String indexName, long node, String key, Object value )
+ throws ExplicitIndexNotFoundKernelException
+ {
+ ktx.assertOpen();
+ allStoreHolder.explicitIndexTxState().nodeChanges( indexName ).addNode( node, key, value );
+ }
+
+ @Override
+ public void nodeRemoveFromExplicitIndex( String indexName, long node ) throws ExplicitIndexNotFoundKernelException
+ {
+ ktx.assertOpen();
+ allStoreHolder.explicitIndexTxState().nodeChanges( indexName ).remove( node );
+ }
+
+ @Override
+ public void nodeRemoveFromExplicitIndex( String indexName, long node, String key, Object value )
+ throws ExplicitIndexNotFoundKernelException
+ {
+ ktx.assertOpen();
+ allStoreHolder.explicitIndexTxState().nodeChanges( indexName ).remove( node, key, value );
+ }
+
+ @Override
+ public void nodeRemoveFromExplicitIndex( String indexName, long node, String key )
+ throws ExplicitIndexNotFoundKernelException
+ {
+ ktx.assertOpen();
+ allStoreHolder.explicitIndexTxState().nodeChanges( indexName ).remove( node, key );
+ }
+
+ @Override
+ public void nodeExplicitIndexCreate( String indexName, Map customConfig )
+ {
+ ktx.assertOpen();
+ allStoreHolder.explicitIndexTxState().createIndex( IndexEntityType.Node, indexName, customConfig );
+ }
+
+ @Override
+ public void nodeExplicitIndexCreateLazily( String indexName, Map customConfig )
+ {
+ ktx.assertOpen();
+ allStoreHolder.getOrCreateNodeIndexConfig( indexName, customConfig );
+ }
+
+ @Override
+ public void nodeExplicitIndexDrop( String indexName ) throws ExplicitIndexNotFoundKernelException
+ {
+ ktx.assertOpen();
+ ExplicitIndexTransactionState txState = allStoreHolder.explicitIndexTxState();
+ txState.nodeChanges( indexName ).drop();
+ txState.deleteIndex( IndexEntityType.Node, indexName );
+ }
+
+ @Override
+ public String nodeExplicitIndexSetConfiguration( String indexName, String key, String value )
+ throws ExplicitIndexNotFoundKernelException
+ {
+ ktx.assertOpen();
+ return allStoreHolder.explicitIndexStore().setNodeIndexConfiguration( indexName, key, value );
+ }
+
+ @Override
+ public String nodeExplicitIndexRemoveConfiguration( String indexName, String key )
+ throws ExplicitIndexNotFoundKernelException
+ {
+ ktx.assertOpen();
+ return allStoreHolder.explicitIndexStore().removeNodeIndexConfiguration( indexName, key );
+ }
+
+ @Override
+ public void relationshipAddToExplicitIndex( String indexName, long relationship, String key, Object value )
+ throws ExplicitIndexNotFoundKernelException, EntityNotFoundException
+ {
+ ktx.assertOpen();
+ allStoreHolder.singleRelationship( relationship, relationshipCursor );
+ if ( relationshipCursor.next() )
+ {
+ allStoreHolder.explicitIndexTxState().relationshipChanges( indexName ).addRelationship( relationship, key, value,
+ relationshipCursor.sourceNodeReference(), relationshipCursor.targetNodeReference() );
+ }
+ else
+ {
+ throw new EntityNotFoundException( EntityType.RELATIONSHIP, relationship );
+ }
+ }
+
+ @Override
+ public void relationshipRemoveFromExplicitIndex( String indexName, long relationship, String key, Object value )
+ throws ExplicitIndexNotFoundKernelException
+ {
+ ktx.assertOpen();
+ allStoreHolder.explicitIndexTxState().relationshipChanges( indexName ).remove( relationship, key, value );
+ }
+
+ @Override
+ public void relationshipRemoveFromExplicitIndex( String indexName, long relationship, String key )
+ throws ExplicitIndexNotFoundKernelException
+ {
+ ktx.assertOpen();
+ allStoreHolder.explicitIndexTxState().relationshipChanges( indexName ).remove( relationship, key );
+
+ }
+
+ @Override
+ public void relationshipRemoveFromExplicitIndex( String indexName, long relationship )
+ throws ExplicitIndexNotFoundKernelException
+ {
+ ktx.assertOpen();
+ allStoreHolder.explicitIndexTxState().relationshipChanges( indexName ).remove( relationship );
+ }
+
+ @Override
+ public void relationshipExplicitIndexCreate( String indexName, Map customConfig )
+ {
+ ktx.assertOpen();
+ allStoreHolder.explicitIndexTxState().createIndex( IndexEntityType.Relationship, indexName, customConfig );
+ }
+
+ @Override
+ public void relationshipExplicitIndexCreateLazily( String indexName, Map customConfig )
+ {
+ ktx.assertOpen();
+ allStoreHolder.getOrCreateRelationshipIndexConfig( indexName, customConfig );
+ }
+
+ @Override
+ public void relationshipExplicitIndexDrop( String indexName ) throws ExplicitIndexNotFoundKernelException
+ {
+ ktx.assertOpen();
+ ExplicitIndexTransactionState txState = allStoreHolder.explicitIndexTxState();
+ txState.relationshipChanges( indexName ).drop();
+ txState.deleteIndex( IndexEntityType.Relationship, indexName );
+ }
+
+ private Value readNodeProperty( int propertyKey )
+ {
+ nodeCursor.properties( propertyCursor );
+
+ //Find out if the property had a value
+ Value existingValue = NO_VALUE;
+ while ( propertyCursor.next() )
+ {
+ if ( propertyCursor.propertyKey() == propertyKey )
+ {
+ existingValue = propertyCursor.propertyValue();
+ break;
+ }
+ }
+ return existingValue;
+ }
+
+ private Value readRelationshipProperty( int propertyKey )
+ {
+ relationshipCursor.properties( propertyCursor );
+
+ //Find out if the property had a value
+ Value existingValue = NO_VALUE;
+ while ( propertyCursor.next() )
+ {
+ if ( propertyCursor.propertyKey() == propertyKey )
+ {
+ existingValue = propertyCursor.propertyValue();
+ break;
+ }
+ }
+ return existingValue;
+ }
+
+ private Value readGraphProperty( int propertyKey )
+ {
+ allStoreHolder.graphProperties( propertyCursor );
+
+ //Find out if the property had a value
+ Value existingValue = NO_VALUE;
+ while ( propertyCursor.next() )
+ {
+ if ( propertyCursor.propertyKey() == propertyKey )
+ {
+ existingValue = propertyCursor.propertyValue();
+ break;
+ }
+ }
+ return existingValue;
+ }
+
+ public CursorFactory cursors()
+ {
+ return cursors;
+ }
+
+ public Procedures procedures()
+ {
+ return allStoreHolder;
+ }
+
+ public void release()
+ {
+ if ( nodeCursor != null )
+ {
+ nodeCursor.close();
+ nodeCursor = null;
+ }
+ if ( propertyCursor != null )
+ {
+ propertyCursor.close();
+ propertyCursor = null;
+ }
+ if ( relationshipCursor != null )
+ {
+ relationshipCursor.close();
+ relationshipCursor = null;
+ }
+
+ cursors.assertClosed();
+ cursors.release();
+ }
+
+ public Token token()
+ {
+ return token;
+ }
+
+ public ExplicitIndexRead indexRead()
+ {
+ return allStoreHolder;
+ }
+
+ public SchemaRead schemaRead()
+ {
+ return allStoreHolder;
+ }
+
+ public Read dataRead()
+ {
+ return allStoreHolder;
+ }
+
+ public DefaultNodeCursor nodeCursor()
+ {
+ return nodeCursor;
+ }
+
+ public DefaultRelationshipScanCursor relationshipCursor()
+ {
+ return relationshipCursor;
+ }
+
+ public DefaultPropertyCursor propertyCursor()
+ {
+ return propertyCursor;
+ }
+
+ @Override
+ public IndexReference indexCreate( SchemaDescriptor descriptor ) throws SchemaKernelException
+ {
+ return indexCreate( descriptor, config.get( GraphDatabaseSettings.default_schema_provider ), Optional.empty() );
+ }
+
+ @Override
+ public IndexReference indexCreate( SchemaDescriptor descriptor, Optional indexName ) throws SchemaKernelException
+ {
+ return indexCreate( descriptor, config.get( GraphDatabaseSettings.default_schema_provider ), indexName );
+ }
+
+ @Override
+ public IndexReference indexCreate( SchemaDescriptor descriptor,
+ String provider,
+ Optional name ) throws SchemaKernelException
+ {
+ exclusiveSchemaLock( descriptor );
+ ktx.assertOpen();
+ assertValidDescriptor( descriptor, SchemaKernelException.OperationContext.INDEX_CREATION );
+ assertIndexDoesNotExist( SchemaKernelException.OperationContext.INDEX_CREATION, descriptor, name );
+
+ IndexProviderDescriptor providerDescriptor = indexingService.indexProviderByName( provider );
+ IndexDescriptor index = IndexDescriptorFactory.forSchema( descriptor, name, providerDescriptor );
+ index = indexingService.getBlessedDescriptorFromProvider( index );
+ ktx.txState().indexDoAdd( index );
+ return index;
+ }
+
+ // Note: this will be sneakily executed by an internal transaction, so no additional locking is required.
+ public IndexDescriptor indexUniqueCreate( SchemaDescriptor schema, String provider ) throws SchemaKernelException
+ {
+ IndexProviderDescriptor providerDescriptor = indexingService.indexProviderByName( provider );
+ IndexDescriptor index =
+ IndexDescriptorFactory.uniqueForSchema( schema,
+ Optional.empty(),
+ providerDescriptor );
+ index = indexingService.getBlessedDescriptorFromProvider( index );
+ ktx.txState().indexDoAdd( index );
+ return index;
+ }
+
+ @Override
+ public void indexDrop( IndexReference indexReference ) throws SchemaKernelException
+ {
+ assertValidIndex( indexReference );
+ IndexDescriptor index = (IndexDescriptor) indexReference;
+ SchemaDescriptor schema = index.schema();
+
+ exclusiveSchemaLock( schema );
+ ktx.assertOpen();
+ try
+ {
+ IndexDescriptor existingIndex = allStoreHolder.indexGetForSchema( schema );
+
+ if ( existingIndex == null )
+ {
+ throw new NoSuchIndexException( schema );
+ }
+
+ if ( existingIndex.type() == UNIQUE )
+ {
+ if ( allStoreHolder.indexGetOwningUniquenessConstraintId( existingIndex ) != null )
+ {
+ throw new IndexBelongsToConstraintException( schema );
+ }
+ }
+ }
+ catch ( IndexBelongsToConstraintException | NoSuchIndexException e )
+ {
+ throw new DropIndexFailureException( schema, e );
+ }
+ ktx.txState().indexDoDrop( index );
+ }
+
+ @Override
+ public ConstraintDescriptor uniquePropertyConstraintCreate( SchemaDescriptor descriptor )
+ throws SchemaKernelException
+ {
+ return uniquePropertyConstraintCreate( descriptor, config.get( GraphDatabaseSettings.default_schema_provider ) );
+ }
+
+ @Override
+ public ConstraintDescriptor uniquePropertyConstraintCreate( SchemaDescriptor descriptor, String provider )
+ throws SchemaKernelException
+ {
+ //Lock
+ exclusiveSchemaLock( descriptor );
+ ktx.assertOpen();
+
+ //Check data integrity
+ assertValidDescriptor( descriptor, SchemaKernelException.OperationContext.CONSTRAINT_CREATION );
+ UniquenessConstraintDescriptor constraint = ConstraintDescriptorFactory.uniqueForSchema( descriptor );
+ assertConstraintDoesNotExist( constraint );
+ // It is not allowed to create uniqueness constraints on indexed label/property pairs
+ assertIndexDoesNotExist( SchemaKernelException.OperationContext.CONSTRAINT_CREATION, descriptor, Optional.empty() );
+
+ // Create constraints
+ indexBackedConstraintCreate( constraint, provider );
+ return constraint;
+ }
+
+ @Override
+ public ConstraintDescriptor nodeKeyConstraintCreate( LabelSchemaDescriptor descriptor ) throws SchemaKernelException
+ {
+ return nodeKeyConstraintCreate( descriptor, config.get( GraphDatabaseSettings.default_schema_provider ) );
+ }
+
+ @Override
+ public ConstraintDescriptor nodeKeyConstraintCreate( LabelSchemaDescriptor descriptor, String provider ) throws SchemaKernelException
+ {
+ //Lock
+ exclusiveSchemaLock( descriptor );
+ ktx.assertOpen();
+
+ //Check data integrity
+ assertValidDescriptor( descriptor, SchemaKernelException.OperationContext.CONSTRAINT_CREATION );
+ NodeKeyConstraintDescriptor constraint = ConstraintDescriptorFactory.nodeKeyForSchema( descriptor );
+ assertConstraintDoesNotExist( constraint );
+ // It is not allowed to create node key constraints on indexed label/property pairs
+ assertIndexDoesNotExist( SchemaKernelException.OperationContext.CONSTRAINT_CREATION, descriptor, Optional.empty() );
+
+ //enforce constraints
+ try ( NodeLabelIndexCursor nodes = cursors.allocateNodeLabelIndexCursor() )
+ {
+ allStoreHolder.nodeLabelScan( descriptor.getLabelId(), nodes );
+ constraintSemantics.validateNodeKeyConstraint( nodes, nodeCursor, propertyCursor, descriptor );
+ }
+
+ //create constraint
+ indexBackedConstraintCreate( constraint, provider );
+ return constraint;
+ }
+
+ @Override
+ public ConstraintDescriptor nodePropertyExistenceConstraintCreate( LabelSchemaDescriptor descriptor )
+ throws SchemaKernelException
+ {
+ //Lock
+ exclusiveSchemaLock( descriptor );
+ ktx.assertOpen();
+
+ //verify data integrity
+ assertValidDescriptor( descriptor, SchemaKernelException.OperationContext.CONSTRAINT_CREATION );
+ ConstraintDescriptor constraint = ConstraintDescriptorFactory.existsForSchema( descriptor );
+ assertConstraintDoesNotExist( constraint );
+
+ //enforce constraints
+ try ( NodeLabelIndexCursor nodes = cursors.allocateNodeLabelIndexCursor() )
+ {
+ allStoreHolder.nodeLabelScan( descriptor.getLabelId(), nodes );
+ constraintSemantics
+ .validateNodePropertyExistenceConstraint( nodes, nodeCursor, propertyCursor, descriptor );
+ }
+
+ //create constraint
+ ktx.txState().constraintDoAdd( constraint );
+ return constraint;
+ }
+
+ @Override
+ public ConstraintDescriptor relationshipPropertyExistenceConstraintCreate( RelationTypeSchemaDescriptor descriptor )
+ throws SchemaKernelException
+ {
+ //Lock
+ exclusiveSchemaLock( descriptor );
+ ktx.assertOpen();
+
+ //verify data integrity
+ assertValidDescriptor( descriptor, SchemaKernelException.OperationContext.CONSTRAINT_CREATION );
+ ConstraintDescriptor constraint = ConstraintDescriptorFactory.existsForSchema( descriptor );
+ assertConstraintDoesNotExist( constraint );
+
+ //enforce constraints
+ allStoreHolder.relationshipTypeScan( descriptor.getRelTypeId(), relationshipCursor );
+ constraintSemantics
+ .validateRelationshipPropertyExistenceConstraint( relationshipCursor, propertyCursor, descriptor );
+
+ //Create
+ ktx.txState().constraintDoAdd( constraint );
+ return constraint;
+
+ }
+
+ @Override
+ public String relationshipExplicitIndexSetConfiguration( String indexName, String key, String value )
+ throws ExplicitIndexNotFoundKernelException
+ {
+ ktx.assertOpen();
+ return allStoreHolder.explicitIndexStore().setRelationshipIndexConfiguration( indexName, key, value );
+ }
+
+ @Override
+ public String relationshipExplicitIndexRemoveConfiguration( String indexName, String key )
+ throws ExplicitIndexNotFoundKernelException
+ {
+ ktx.assertOpen();
+ return allStoreHolder.explicitIndexStore().removeRelationshipIndexConfiguration( indexName, key );
+ }
+
+ @Override
+ public void constraintDrop( ConstraintDescriptor descriptor ) throws SchemaKernelException
+ {
+ //Lock
+ SchemaDescriptor schema = descriptor.schema();
+ exclusiveOptimisticLock( schema.keyType(), schema.keyId() );
+ ktx.assertOpen();
+
+ //verify data integrity
+ try
+ {
+ assertConstraintExists( descriptor );
+ }
+ catch ( NoSuchConstraintException e )
+ {
+ throw new DropConstraintFailureException( descriptor, e );
+ }
+
+ //Drop it like it's hot
+ ktx.txState().constraintDoDrop( descriptor );
+ }
+
+ private void assertIndexDoesNotExist( SchemaKernelException.OperationContext context, SchemaDescriptor descriptor, Optional name )
+ throws AlreadyIndexedException, AlreadyConstrainedException
+ {
+ IndexDescriptor existingIndex = allStoreHolder.indexGetForSchema( descriptor );
+ if ( existingIndex == null && name.isPresent() )
+ {
+ IndexReference indexReference = allStoreHolder.indexGetForName( name.get() );
+ if ( indexReference != IndexReference.NO_INDEX )
+ {
+ existingIndex = (IndexDescriptor) indexReference;
+ }
+ }
+ if ( existingIndex != null )
+ {
+ // OK so we found a matching constraint index. We check whether or not it has an owner
+ // because this may have been a left-over constraint index from a previously failed
+ // constraint creation, due to crash or similar, hence the missing owner.
+ if ( existingIndex.type() == UNIQUE )
+ {
+ if ( context != CONSTRAINT_CREATION || constraintIndexHasOwner( existingIndex ) )
+ {
+ throw new AlreadyConstrainedException( ConstraintDescriptorFactory.uniqueForSchema( descriptor ),
+ context, new SilentTokenNameLookup( token ) );
+ }
+ }
+ else
+ {
+ throw new AlreadyIndexedException( descriptor, context );
+ }
+ }
+ }
+
+ private void exclusiveOptimisticLock( ResourceType resource, long resourceId )
+ {
+ ktx.statementLocks().optimistic().acquireExclusive( ktx.lockTracer(), resource, resourceId );
+ }
+
+ private void acquireExclusiveNodeLock( long node )
+ {
+ if ( !ktx.hasTxStateWithChanges() || !ktx.txState().nodeIsAddedInThisTx( node ) )
+ {
+ ktx.statementLocks().optimistic().acquireExclusive( ktx.lockTracer(), ResourceTypes.NODE, node );
+ }
+ }
+
+ private void acquireExclusiveRelationshipLock( long relationshipId )
+ {
+ if ( !ktx.hasTxStateWithChanges() || !ktx.txState().relationshipIsAddedInThisTx( relationshipId ) )
+ {
+ ktx.statementLocks().optimistic()
+ .acquireExclusive( ktx.lockTracer(), ResourceTypes.RELATIONSHIP, relationshipId );
+ }
+ }
+
+ private void sharedSchemaLock( ResourceType type, int tokenId )
+ {
+ ktx.statementLocks().optimistic().acquireShared( ktx.lockTracer(), type, tokenId );
+ }
+
+ private void exclusiveSchemaLock( SchemaDescriptor schema )
+ {
+ long[] lockingIds = schemaTokenLockingIds( schema );
+ ktx.statementLocks().optimistic().acquireExclusive( ktx.lockTracer(), schema.keyType(), lockingIds );
+ }
+
+ private void lockRelationshipNodes( long startNodeId, long endNodeId )
+ {
+ // Order the locks to lower the risk of deadlocks with other threads creating/deleting rels concurrently
+ acquireExclusiveNodeLock( min( startNodeId, endNodeId ) );
+ if ( startNodeId != endNodeId )
+ {
+ acquireExclusiveNodeLock( max( startNodeId, endNodeId ) );
+ }
+ }
+
+ private static boolean propertyHasChanged( Value lhs, Value rhs )
+ {
+ //It is not enough to check equality here since by our equality semantics `int == tofloat(int)` is `true`
+ //so by only checking for equality users cannot change type of property without also "changing" the value.
+ //Hence the extra type check here.
+ return lhs.getClass() != rhs.getClass() || !lhs.equals( rhs );
+ }
+
+ private void assertNodeExists( long sourceNode ) throws EntityNotFoundException
+ {
+ if ( !allStoreHolder.nodeExists( sourceNode ) )
+ {
+ throw new EntityNotFoundException( NODE, sourceNode );
+ }
+ }
+
+ private boolean constraintIndexHasOwner( IndexDescriptor descriptor )
+ {
+ return allStoreHolder.indexGetOwningUniquenessConstraintId( descriptor ) != null;
+ }
+
+ private void assertConstraintDoesNotExist( ConstraintDescriptor constraint )
+ throws AlreadyConstrainedException
+ {
+ if ( allStoreHolder.constraintExists( constraint ) )
+ {
+ throw new AlreadyConstrainedException( constraint,
+ SchemaKernelException.OperationContext.CONSTRAINT_CREATION,
+ new SilentTokenNameLookup( token ) );
+ }
+ }
+
+ public Locks locks()
+ {
+ return allStoreHolder;
+ }
+
+ private void assertConstraintExists( ConstraintDescriptor constraint )
+ throws NoSuchConstraintException
+ {
+ if ( !allStoreHolder.constraintExists( constraint ) )
+ {
+ throw new NoSuchConstraintException( constraint );
+ }
+ }
+
+ private static void assertValidDescriptor( SchemaDescriptor descriptor, SchemaKernelException.OperationContext context )
+ throws RepeatedPropertyInCompositeSchemaException
+ {
+ int numUnique = Arrays.stream( descriptor.getPropertyIds() ).distinct().toArray().length;
+ if ( numUnique != descriptor.getPropertyIds().length )
+ {
+ throw new RepeatedPropertyInCompositeSchemaException( descriptor, context );
+ }
+ }
+
+ private void indexBackedConstraintCreate( IndexBackedConstraintDescriptor constraint, String provider )
+ throws CreateConstraintFailureException
+ {
+ SchemaDescriptor descriptor = constraint.schema();
+ try
+ {
+ if ( ktx.hasTxStateWithChanges() &&
+ ktx.txState().indexDoUnRemove( constraint.ownedIndexDescriptor() ) ) // ..., DROP, *CREATE*
+ { // creation is undoing a drop
+ if ( !ktx.txState().constraintDoUnRemove( constraint ) ) // CREATE, ..., DROP, *CREATE*
+ { // ... the drop we are undoing did itself undo a prior create...
+ ktx.txState().constraintDoAdd(
+ constraint, ktx.txState().indexCreatedForConstraint( constraint ) );
+ }
+ }
+ else // *CREATE*
+ { // create from scratch
+ Iterator it = allStoreHolder.constraintsGetForSchema( descriptor );
+ while ( it.hasNext() )
+ {
+ if ( it.next().equals( constraint ) )
+ {
+ return;
+ }
+ }
+ long indexId = constraintIndexCreator.createUniquenessConstraintIndex( ktx, descriptor, provider );
+ if ( !allStoreHolder.constraintExists( constraint ) )
+ {
+ // This looks weird, but since we release the label lock while awaiting population of the index
+ // backing this constraint there can be someone else getting ahead of us, creating this exact
+ // constraint
+ // before we do, so now getting out here under the lock we must check again and if it exists
+ // we must at this point consider this an idempotent operation because we verified earlier
+ // that it didn't exist and went on to create it.
+ ktx.txState().constraintDoAdd( constraint, indexId );
+ }
+ }
+ }
+ catch ( UniquePropertyValueValidationException | TransactionFailureException | AlreadyConstrainedException e )
+ {
+ throw new CreateConstraintFailureException( constraint, e );
+ }
+ }
+
+ private static void assertValidIndex( IndexReference index ) throws NoSuchIndexException
+ {
+ if ( index == IndexReference.NO_INDEX )
+ {
+ throw new NoSuchIndexException( index.schema() );
+ }
+ }
+}
diff --git a/external-properties/src/main/scala/cn/pandadb/externalprops/ExternalPropertyStore.scala b/external-properties/src/main/scala/cn/pandadb/externalprops/ExternalPropertyStore.scala
new file mode 100644
index 00000000..37bc79fc
--- /dev/null
+++ b/external-properties/src/main/scala/cn/pandadb/externalprops/ExternalPropertyStore.scala
@@ -0,0 +1,369 @@
+package cn.pandadb.externalprops
+
+import cn.pandadb.util.{ClosableModuleComponent, Configuration, PandaException}
+import org.neo4j.cypher.internal.runtime.interpreted.NFPredicate
+import org.neo4j.values.storable.{Value, Values}
+import org.neo4j.values.virtual.{NodeValue, VirtualValues}
+
+import scala.collection.mutable
+import scala.collection.mutable.ArrayBuffer
+
+/**
+ * Created by bluejoe on 2019/10/7.
+ */
+trait ExternalPropertyStoreFactory {
+ def create(conf: Configuration): CustomPropertyNodeStore;
+}
+
+trait CustomPropertyNodeReader {
+ def filterNodes(expr: NFPredicate): Iterable[Long];
+
+ def getNodesByLabel(label: String): Iterable[Long];
+
+ def getNodeBylabelAndFilter(label: String, expr: NFPredicate): Iterable[Long];
+
+ @deprecated
+ def getNodeById(id: Long): Option[NodeWithProperties];
+}
+
+trait CustomPropertyNodeStore extends CustomPropertyNodeReader with ClosableModuleComponent {
+ def beginWriteTransaction(): PropertyWriteTransaction;
+}
+
+trait PropertyWriter {
+ def deleteNode(nodeId: Long);
+
+ def addNode(nodeId: Long);
+
+ def addProperty(nodeId: Long, key: String, value: Value): Unit;
+
+ def removeProperty(nodeId: Long, key: String);
+
+ def updateProperty(nodeId: Long, key: String, value: Value): Unit;
+
+ def addLabel(nodeId: Long, label: String): Unit;
+
+ def removeLabel(nodeId: Long, label: String): Unit;
+}
+
+trait PropertyReaderWithinTransaction {
+ def getNodeLabels(nodeId: Long): Array[String];
+
+ def getPropertyValue(nodeId: Long, key: String): Option[Value];
+}
+
+trait PropertyWriteTransaction extends PropertyWriter with PropertyReaderWithinTransaction {
+ @throws[FailedToCommitTransaction]
+ def commit(): mutable.Undoable;
+
+ @throws[FailedToRollbackTransaction]
+ def rollback(): Unit;
+
+ def close(): Unit;
+}
+
+class FailedToCommitTransaction(tx: PropertyWriteTransaction, cause: Throwable)
+ extends PandaException("failed to commit transaction: $tx") {
+
+}
+
+class FailedToRollbackTransaction(tx: PropertyWriteTransaction, cause: Throwable)
+ extends PandaException("failed to roll back transaction: $tx") {
+
+}
+
+case class NodeWithProperties(id: Long, props: Map[String, Value], labels: Iterable[String]) {
+ def toNeo4jNodeValue(): NodeValue = {
+ VirtualValues.nodeValue(id,
+ Values.stringArray(labels.toArray: _*),
+ VirtualValues.map(props.keys.toArray, props.values.toArray))
+ }
+
+ def mutable(): MutableNodeWithProperties = {
+ val m = MutableNodeWithProperties(id);
+ m.props ++= props;
+ m.labels ++= labels;
+ m;
+ }
+}
+
+case class MutableNodeWithProperties(id: Long) {
+ val props = mutable.Map[String, Value]();
+ val labels = ArrayBuffer[String]();
+}
+
+class BufferedExternalPropertyWriteTransaction(
+ nodeReader: CustomPropertyNodeReader,
+ commitPerformer: GroupedOpVisitor,
+ undoPerformer: GroupedOpVisitor)
+ extends PropertyWriteTransaction {
+ val bufferedOps = ArrayBuffer[BufferedPropertyOp]();
+ val oldState = mutable.Map[Long, MutableNodeWithProperties]();
+ val newState = mutable.Map[Long, MutableNodeWithProperties]();
+ private var isCommited = false
+ override def deleteNode(nodeId: Long): Unit = {
+ getPopulatedNode(nodeId)
+ //bufferedOps += BufferedDeleteNodeOp(nodeId)
+ newState.remove(nodeId)
+ }
+
+ //get node related info when required
+ private def getPopulatedNode(nodeId: Long): MutableNodeWithProperties = {
+
+ if (isNodeExitInNewState(nodeId)) newState.get(nodeId).get
+ else {
+ if (!isNodeExitInOldState(nodeId)) {
+ val state = nodeReader.getNodeById(nodeId).get //get from database,if failue throw exception no such node
+ oldState += nodeId -> state.mutable()
+ newState += nodeId -> state.mutable()
+ newState.get(nodeId).get
+ }
+ else null //throw exception ,node deleted
+ }
+
+ }
+ private def isNodeExitInNewState(nodeId: Long): Boolean = {
+ newState.contains(nodeId)
+ }
+ private def isNodeExitInOldState(nodeId: Long): Boolean = {
+ oldState.contains(nodeId)
+ }
+
+ override def addNode(nodeId: Long): Unit = {
+ //bufferedOps += BufferedAddNodeOp(nodeId)
+ if (isNodeExitInNewState(nodeId)) null //throw exception node already exist
+ else newState += nodeId -> MutableNodeWithProperties(nodeId)
+ }
+
+ override def addProperty(nodeId: Long, key: String, value: Value): Unit = {
+ //bufferedOps += BufferedAddPropertyOp(nodeId, key, value)
+ val state = getPopulatedNode(nodeId)
+ state.props += (key -> value);
+ newState += nodeId -> state
+ }
+
+ override def removeProperty(nodeId: Long, key: String): Unit = {
+ //bufferedOps += BufferedRemovePropertyOp(nodeId, key)
+ val state = getPopulatedNode(nodeId)
+ state.props -= key;
+ newState += nodeId -> state
+ }
+
+ override def updateProperty(nodeId: Long, key: String, value: Value): Unit = {
+ //bufferedOps += BufferedUpdatePropertyOp(nodeId, key, value)
+ val state = getPopulatedNode(nodeId)
+ state.props += (key -> value);
+ newState += nodeId -> state
+ }
+
+ override def addLabel(nodeId: Long, label: String): Unit = {
+ //bufferedOps += BufferedAddLabelOp(nodeId, label)
+ val state = getPopulatedNode(nodeId)
+ state.labels += label
+ newState += nodeId -> state
+ }
+
+ override def removeLabel(nodeId: Long, label: String): Unit = {
+ //bufferedOps += BufferedRemoveLabelOp(nodeId, label)
+ //getPopulatedNode(nodeId).labels -= label
+ val state = getPopulatedNode(nodeId)
+ state.labels -= label
+ newState += nodeId -> state
+ }
+
+ def getNodeLabels(nodeId: Long): Array[String] = {
+ getPopulatedNode(nodeId).labels.toArray
+ }
+
+ def getPropertyValue(nodeId: Long, key: String): Option[Value] = {
+ getPopulatedNode(nodeId).props.get(key)
+ }
+
+ @throws[FailedToCommitTransaction]
+ def commit(): mutable.Undoable = {
+ val ops: GroupedOps = GroupedOps(bufferedOps.toArray)
+ ops.newState = this.newState
+ ops.oldState = this.oldState
+ if (!isCommited) {
+ doPerformerWork(ops, commitPerformer)
+ isCommited = true
+ }
+ else {
+ Unit //throw exception,cannot commit twice
+ }
+ new mutable.Undoable() {
+ def undo(): Unit = {
+ if (isCommited) {
+ doPerformerWork(ops, undoPerformer)
+ isCommited = false
+ }
+ }
+ }
+ }
+
+ @throws[FailedToRollbackTransaction]
+ def rollback(): Unit = {
+ }
+
+ def close(): Unit = {
+ bufferedOps.clear()
+ newState.clear()
+ oldState.clear()
+ }
+
+ private def doPerformerWork(ops: GroupedOps, performer: GroupedOpVisitor): Unit = {
+ performer.start(ops)
+ performer.work()
+ performer.end(ops)
+ }
+}
+
+/**
+ * buffer based implementation of ExternalPropertyWriteTransaction
+ * this is a template class which should be derived
+ */
+
+case class GroupedOps(ops: Array[BufferedPropertyOp]) {
+ //commands-->combined
+ val addedNodes = mutable.Map[Long, GroupedAddNodeOp]();
+ val updatedNodes = mutable.Map[Long, GroupedUpdateNodeOp]();
+ val deleteNodes = ArrayBuffer[GroupedDeleteNodeOp]();
+ var oldState = mutable.Map[Long, MutableNodeWithProperties]();
+ var newState = mutable.Map[Long, MutableNodeWithProperties]();
+
+ ops.foreach {
+ _ match {
+ case BufferedAddNodeOp(nodeId: Long) => addedNodes += nodeId -> GroupedAddNodeOp(nodeId)
+ case BufferedDeleteNodeOp(nodeId: Long) => deleteNodes += GroupedDeleteNodeOp(nodeId)
+ case BufferedDeleteNodeOp(nodeId: Long) =>
+ addedNodes -= nodeId
+ updatedNodes -= nodeId
+ deleteNodes += GroupedDeleteNodeOp(nodeId)
+ case BufferedUpdatePropertyOp(nodeId: Long, key: String, value: Value) =>
+ if (addedNodes.isDefinedAt(nodeId)) {
+ addedNodes(nodeId).addedProps += key -> value
+ }
+ if (updatedNodes.isDefinedAt(nodeId)) {
+ updatedNodes(nodeId).updatedProps += key -> value
+ }
+ case BufferedRemovePropertyOp(nodeId: Long, key: String) =>
+ if (addedNodes.isDefinedAt(nodeId)) {
+ addedNodes(nodeId).addedProps -= key
+ }
+ if (updatedNodes.isDefinedAt(nodeId)) {
+ updatedNodes(nodeId).updatedProps -= key
+ }
+ case BufferedAddPropertyOp(nodeId: Long, key: String, value: Value) =>
+ if (addedNodes.isDefinedAt(nodeId)) {
+ addedNodes(nodeId).addedProps += key -> value
+ }
+ if (updatedNodes.isDefinedAt(nodeId)) {
+ updatedNodes(nodeId).addedProps += key -> value
+ }
+ case BufferedAddLabelOp(nodeId: Long, label: String) =>
+ if (addedNodes.isDefinedAt(nodeId)) {
+ addedNodes(nodeId).addedLabels += label
+ }
+ if (updatedNodes.isDefinedAt(nodeId)) {
+ updatedNodes(nodeId).addedLabels += label
+ }
+ case BufferedRemoveLabelOp(nodeId: Long, label: String) =>
+ if (addedNodes.isDefinedAt(nodeId)) {
+ addedNodes(nodeId).addedLabels -= label
+ }
+ if (updatedNodes.isDefinedAt(nodeId)) {
+ updatedNodes(nodeId).removedLabels += label
+ }
+ }
+ }
+
+ def accepts(visitor: GroupedOpVisitor): Unit = {
+ addedNodes.values.foreach(_.accepts(visitor))
+ updatedNodes.values.foreach(_.accepts(visitor))
+ deleteNodes.foreach(_.accepts(visitor))
+ }
+}
+
+trait GroupedOpVisitor {
+
+ def start(ops: GroupedOps);
+ def work();
+ def end(ops: GroupedOps);
+
+ def visitAddNode(nodeId: Long, props: Map[String, Value], labels: Array[String]);
+
+ def visitDeleteNode(nodeId: Long);
+
+ def visitUpdateNode(nodeId: Long, addedProps: Map[String, Value], updateProps: Map[String, Value], removeProps: Array[String],
+ addedLabels: Array[String], removedLabels: Array[String]);
+}
+
+/**
+ * buffered operation within a prepared transaction
+ */
+trait BufferedPropertyOp {
+
+}
+
+case class BufferedDeleteNodeOp(nodeId: Long) extends BufferedPropertyOp {
+
+}
+
+case class BufferedAddNodeOp(nodeId: Long) extends BufferedPropertyOp {
+
+}
+
+case class BufferedUpdatePropertyOp(nodeId: Long, key: String, value: Value) extends BufferedPropertyOp {
+
+}
+
+case class BufferedRemovePropertyOp(nodeId: Long, key: String) extends BufferedPropertyOp {
+
+}
+
+case class BufferedAddPropertyOp(nodeId: Long, key: String, value: Value) extends BufferedPropertyOp {
+
+}
+
+case class BufferedAddLabelOp(nodeId: Long, label: String) extends BufferedPropertyOp {
+
+}
+
+case class BufferedRemoveLabelOp(nodeId: Long, label: String) extends BufferedPropertyOp {
+
+}
+
+/**
+ * grouped operation to be committed
+ */
+trait GroupedOp {
+ def accepts(visitor: GroupedOpVisitor): Unit;
+}
+
+case class GroupedAddNodeOp(nodeId: Long) extends GroupedOp {
+ val addedProps = mutable.Map[String, Value]();
+ val addedLabels = mutable.Set[String]();
+
+ def accepts(visitor: GroupedOpVisitor): Unit = {
+ visitor.visitAddNode(nodeId, addedProps.toMap, addedLabels.toArray)
+ }
+}
+
+case class GroupedUpdateNodeOp(nodeId: Long) extends GroupedOp {
+ val addedProps = mutable.Map[String, Value]();
+ val updatedProps = mutable.Map[String, Value]();
+ val removedProps = mutable.Set[String]();
+ val addedLabels = mutable.Set[String]();
+ val removedLabels = mutable.Set[String]();
+
+ def accepts(visitor: GroupedOpVisitor): Unit = {
+ visitor.visitUpdateNode(nodeId, addedProps.toMap, updatedProps.toMap,
+ removedProps.toArray, addedLabels.toArray, removedLabels.toArray)
+ }
+}
+
+case class GroupedDeleteNodeOp(nodeId: Long) extends GroupedOp {
+ def accepts(visitor: GroupedOpVisitor): Unit = {
+ visitor.visitDeleteNode(nodeId)
+ }
+}
\ No newline at end of file
diff --git a/external-properties/src/main/scala/cn/pandadb/externalprops/InElasticSearchPropertyNodeStore.scala b/external-properties/src/main/scala/cn/pandadb/externalprops/InElasticSearchPropertyNodeStore.scala
new file mode 100644
index 00000000..2fa34e86
--- /dev/null
+++ b/external-properties/src/main/scala/cn/pandadb/externalprops/InElasticSearchPropertyNodeStore.scala
@@ -0,0 +1,553 @@
+package cn.pandadb.externalprops
+
+import java.util
+
+import scala.collection.JavaConversions._
+import scala.collection.{AbstractIterator, mutable}
+import scala.collection.mutable.ArrayBuffer
+import org.neo4j.cypher.internal.runtime.interpreted.{NFLessThan, NFPredicate, _}
+import org.neo4j.values.storable._
+import cn.pandadb.util.{Configuration, PandaModuleContext}
+import com.alibaba.fastjson.JSONObject
+import org.apache.http.HttpHost
+import org.elasticsearch.client.{RequestOptions, RestClient, RestHighLevelClient}
+import org.elasticsearch.action.admin.indices.create.{CreateIndexRequest, CreateIndexResponse}
+import org.elasticsearch.action.admin.indices.get.GetIndexRequest
+import org.elasticsearch.action.index.{IndexRequest, IndexResponse}
+import org.elasticsearch.common.xcontent.{XContentBuilder, XContentFactory, XContentType}
+import org.elasticsearch.action.get.GetRequest
+import org.elasticsearch.action.update.{UpdateRequest, UpdateResponse}
+import org.elasticsearch.action.delete.{DeleteRequest, DeleteResponse}
+import org.elasticsearch.common.Strings
+import org.elasticsearch.search.fetch.subphase.FetchSourceContext
+import org.elasticsearch.action.search.{ClearScrollRequest, SearchRequest, SearchScrollRequest}
+import org.elasticsearch.index.query.{QueryBuilder, QueryBuilders}
+import org.elasticsearch.search.builder.SearchSourceBuilder
+import org.elasticsearch.index.reindex.{BulkByScrollResponse, DeleteByQueryRequest}
+import org.elasticsearch.action.support.WriteRequest
+import org.elasticsearch.common.unit.{TimeValue => EsTimeValue}
+import org.elasticsearch.search.{Scroll, SearchHit}
+
+
+class InElasticSearchPropertyNodeStoreFactory extends ExternalPropertyStoreFactory {
+ override def create(conf: Configuration): CustomPropertyNodeStore = {
+
+ import cn.pandadb.util.ConfigUtils._
+
+ val host = conf.getRequiredValueAsString("external.properties.store.es.host")
+ val port = conf.getRequiredValueAsInt("external.properties.store.es.port")
+ val schema = conf.getRequiredValueAsString("external.properties.store.es.schema")
+ val scrollSize = conf.getRequiredValueAsInt("external.properties.store.es.scroll.size")
+ val scrollContainTime = conf.getRequiredValueAsInt("external.properties.store.es.scroll.time.minutes")
+ val indexName = conf.getRequiredValueAsString("external.properties.store.es.index")
+ val typeName = conf.getRequiredValueAsString("external.properties.store.es.type")
+ new InElasticSearchPropertyNodeStore(host, port, indexName, typeName, schema, scrollSize, scrollContainTime)
+ }
+}
+
+object EsUtil {
+ val idName = "id"
+ val labelName = "labels"
+ val tik = "id,labels,_version_"
+ val arrayName = "Array"
+ val dateType = "time"
+
+ def getValueFromArray(value: Array[AnyRef]): Value = {
+ val typeObj = value.head
+ typeObj match {
+ case s1: java.lang.String =>
+ val strArr = value.map(_.toString).toArray
+ val result = Values.stringArray(strArr: _*)
+ result
+ case s2: java.lang.Boolean =>
+ Values.booleanArray(value.map(_.asInstanceOf[Boolean]).toArray)
+ case s3: java.lang.Long =>
+ Values.longArray(value.map(_.asInstanceOf[Long]).toArray)
+ case s4: java.lang.Byte =>
+ Values.byteArray(value.map(_.asInstanceOf[Byte]).toArray)
+ case s5: java.lang.Short =>
+ Values.shortArray(value.map(_.asInstanceOf[Short]).toArray)
+ case s6: java.lang.Integer =>
+ Values.intArray(value.map(_.asInstanceOf[Int]).toArray)
+ case s7: java.lang.Double =>
+ Values.doubleArray(value.map(_.asInstanceOf[Double]).toArray)
+ case s8: java.lang.Float =>
+ Values.floatArray(value.map(_.asInstanceOf[Float]).toArray)
+ case _ => null
+ }
+ }
+
+ def neo4jValueToScala(value: Value): Any = {
+ value match {
+ case v: IntegralValue => v.asInstanceOf[IntegralValue].longValue()
+ case v: IntegralArray =>
+ v.asInstanceOf[IntegralArray].iterator().map(v2 => v2.asInstanceOf[IntegralValue].longValue()).toArray
+ case v: FloatingPointValue => v.asInstanceOf[FloatingPointValue].doubleValue()
+ case v: FloatingPointArray =>
+ v.asInstanceOf[FloatingPointArray].iterator().map(v2 => v2.asInstanceOf[FloatingPointValue].doubleValue()).toArray
+ case v: TextValue => v.asInstanceOf[TextValue].stringValue()
+ case v: TextArray =>
+ v.asInstanceOf[TextArray].iterator().map(v2 => v2.asInstanceOf[TextValue].stringValue()).toArray
+ case v: BooleanValue => v.asInstanceOf[BooleanValue].booleanValue()
+ case v: BooleanArray =>
+ v.asInstanceOf[BooleanArray].iterator().map(v2 => v2.asInstanceOf[BooleanValue].booleanValue()).toArray
+ case v => v.asObject()
+ }
+ }
+
+ def sourceMapToNodeWithProperties(doc: Map[String, Object]): NodeWithProperties = {
+ val props = mutable.Map[String, Value]()
+ var id: Long = -1
+ if (doc.contains(idName)) {
+ id = doc.get(idName).get.asInstanceOf[Int].toLong
+ }
+ val labels = ArrayBuffer[String]()
+ if (doc.contains(labelName)) doc.get(labelName).get.asInstanceOf[util.ArrayList[String]].foreach(u => labels += u)
+ doc.map(field =>
+ if (!field._1.equals(idName) && !field._1.equals(labelName) ) {
+ if (field._2.isInstanceOf[util.ArrayList[Object]]) {
+ props(field._1) = getValueFromArray(field._2.asInstanceOf[util.ArrayList[Object]].toArray())
+ }
+ else props(field._1) = Values.of(field._2)
+ }
+ )
+
+ NodeWithProperties(id.toString.toLong, props.toMap, labels)
+ }
+
+ def createClient(host: String, port: Int, indexName: String, typeName: String,
+ schema: String = "http") : RestHighLevelClient = {
+ val httpHost = new HttpHost(host, port, schema)
+ val builder = RestClient.builder(httpHost)
+ val client = new RestHighLevelClient(builder)
+ if (!indexExists(client, indexName)) {
+ val res = createIndex(client, indexName, typeName)
+ if (!res) throw new Exception("InElasticSearchPropertyNodeStore: create index failed!")
+ }
+ client
+ }
+
+ private def indexExists(client: RestHighLevelClient, indexName: String): Boolean = {
+ val request = new GetIndexRequest()
+ request.indices(indexName)
+ client.indices.exists(request, RequestOptions.DEFAULT)
+ }
+
+ private def createIndex(client: RestHighLevelClient, indexName: String, typeName: String): Boolean = {
+ val indexRequest: CreateIndexRequest = new CreateIndexRequest(indexName)
+ indexRequest.mapping(typeName, "{\"_all\":{\"type\":\"text\"}}", XContentType.JSON)
+ val indexResponse: CreateIndexResponse = client.indices().create(indexRequest, RequestOptions.DEFAULT)
+ indexResponse.isAcknowledged
+ }
+
+ def addData(client: RestHighLevelClient, indexName: String, typeName: String, id: String, builder: XContentBuilder): String = {
+ val indexRequest: IndexRequest = new IndexRequest(indexName, typeName, id)
+ indexRequest.source(builder)
+ indexRequest.setRefreshPolicy(WriteRequest.RefreshPolicy.WAIT_UNTIL)
+ val indexResponse: IndexResponse = client.index(indexRequest)
+ indexResponse.getId
+ }
+
+ def updateData(client: RestHighLevelClient, indexName: String, typeName: String, id: String, data: JSONObject): String = {
+ val request = new UpdateRequest(indexName, typeName, id)
+ request.doc(data.toString, XContentType.JSON)
+ request.setRefreshPolicy(WriteRequest.RefreshPolicy.WAIT_UNTIL)
+ val response: UpdateResponse = client.update(request, RequestOptions.DEFAULT)
+ response.toString
+ }
+
+ def deleteData(client: RestHighLevelClient, indexName: String, typeName: String, id: String): String = {
+ val request: DeleteRequest = new DeleteRequest(indexName, typeName, id)
+ request.setRefreshPolicy(WriteRequest.RefreshPolicy.WAIT_UNTIL)
+ val response: DeleteResponse = client.delete(request, RequestOptions.DEFAULT)
+ response.toString
+ }
+
+ def getData(client: RestHighLevelClient, indexName: String, typeName: String, id: String): mutable.Map[String, Object] = {
+ val request: GetRequest = new GetRequest(indexName, typeName, id)
+ val includes = Strings.EMPTY_ARRAY
+ val excludes = Strings.EMPTY_ARRAY
+ val fetchSourceContext = new FetchSourceContext(true, includes, excludes)
+ request.fetchSourceContext(fetchSourceContext)
+ val response = client.get(request, RequestOptions.DEFAULT)
+ response.getSource
+ }
+
+ def getAllSize(client: RestHighLevelClient, indexName: String, typeName: String): Long = {
+ val searchRequest: SearchRequest = new SearchRequest();
+ searchRequest.indices(indexName)
+ searchRequest.types(typeName)
+ val searchSourceBuilder = new SearchSourceBuilder();
+ searchSourceBuilder.query(QueryBuilders.matchAllQuery());
+ searchSourceBuilder.fetchSource(false)
+ searchRequest.source(searchSourceBuilder);
+ val searchResponse = client.search(searchRequest, RequestOptions.DEFAULT)
+ searchResponse.getHits.totalHits
+ }
+
+ def clearAllData(client: RestHighLevelClient, indexName: String, typeName: String): Long = {
+ val request: DeleteByQueryRequest = new DeleteByQueryRequest()
+ request.indices(indexName)
+ request.types(typeName)
+ request.setQuery(QueryBuilders.matchAllQuery())
+ request.setRefresh(true)
+ val response: BulkByScrollResponse = client.deleteByQuery(request, RequestOptions.DEFAULT)
+ response.getDeleted
+ }
+
+ def searchWithProperties(client: RestHighLevelClient, indexName: String, typeName: String,
+ queryBuilder: QueryBuilder, scrollSize: Int, scrollContainTimeMinutes: Int): Iterable[NodeWithProperties] = {
+ (new SearchResultsIterator(client, indexName, typeName, queryBuilder, scrollSize, scrollContainTimeMinutes)).toIterable
+ }
+
+ def searchNodeId(client: RestHighLevelClient, indexName: String, typeName: String,
+ queryBuilder: QueryBuilder, scrollSize: Int, scrollContainTimeMinutes: Int): Iterable[Long] = {
+ (new SearchNodeIdResultsIterator(client, indexName, typeName, queryBuilder, scrollSize, scrollContainTimeMinutes)).toIterable
+ }
+
+ class SearchResultsIterator(client: RestHighLevelClient, indexName: String, typeName: String, queryBuilder: QueryBuilder,
+ scrollSize: Int, scrollContainTimeMinutes: Int) extends AbstractIterator[NodeWithProperties] {
+ private val searchRequest = new SearchRequest()
+ searchRequest.indices(indexName)
+ searchRequest.types(typeName)
+ private val searchSourceBuilder = new SearchSourceBuilder()
+ private val scroll = new Scroll(EsTimeValue.timeValueMinutes(scrollContainTimeMinutes))
+ searchSourceBuilder.query(queryBuilder)
+ searchSourceBuilder.size(scrollSize)
+ searchRequest.source(searchSourceBuilder)
+ searchRequest.scroll(scroll)
+ private var searchResponse = client.search(searchRequest, RequestOptions.DEFAULT)
+ private var scrollId = searchResponse.getScrollId
+ private var searchHits = searchResponse.getHits.getHits
+ private var lastHitsSize = searchHits.size
+ private var hitsIterator = searchHits.toIterator
+
+ private def doScroll(): Boolean = {
+ if (lastHitsSize > 0) {
+ val scrollRequest = new SearchScrollRequest(scrollId)
+ scrollRequest.scroll(scroll)
+ searchResponse = client.scroll(scrollRequest, RequestOptions.DEFAULT)
+ scrollId = searchResponse.getScrollId
+ searchHits = searchResponse.getHits.getHits
+ lastHitsSize = searchHits.size
+ hitsIterator = searchHits.toIterator
+ lastHitsSize > 0
+ }
+ else {
+ val clearScrollRequest = new ClearScrollRequest
+ clearScrollRequest.addScrollId(scrollId)
+ val clearScrollResponse = client.clearScroll(clearScrollRequest, RequestOptions.DEFAULT)
+ clearScrollResponse.isSucceeded
+ false
+ }
+ }
+
+ override def hasNext: Boolean = hitsIterator.hasNext || doScroll()
+
+ override def next(): NodeWithProperties = {
+ val h = hitsIterator.next()
+ EsUtil.sourceMapToNodeWithProperties(h.getSourceAsMap.toMap)
+ }
+ }
+
+ class SearchNodeIdResultsIterator(client: RestHighLevelClient, indexName: String, typeName: String, queryBuilder: QueryBuilder,
+ scrollSize: Int, scrollContainTimeMinutes: Int) extends AbstractIterator[Long] {
+ private val searchRequest = new SearchRequest()
+ searchRequest.indices(indexName)
+ searchRequest.types(typeName)
+ private val searchSourceBuilder = new SearchSourceBuilder()
+ private val scroll = new Scroll(EsTimeValue.timeValueMinutes(scrollContainTimeMinutes))
+ searchSourceBuilder.query(queryBuilder)
+ val fields = Array[String](idName)
+ searchSourceBuilder.fetchSource(fields, null)
+ searchSourceBuilder.size(scrollSize)
+ searchRequest.source(searchSourceBuilder)
+ searchRequest.scroll(scroll)
+ private var searchResponse = client.search(searchRequest, RequestOptions.DEFAULT)
+ private var scrollId = searchResponse.getScrollId
+ private var searchHits: Array[SearchHit] = searchResponse.getHits.getHits
+ private var lastHitsSize = searchHits.size
+ private var hitsIterator = searchHits.toIterator
+
+ private def doScroll(): Boolean = {
+ if (lastHitsSize > 0) {
+ val scrollRequest = new SearchScrollRequest(scrollId)
+ scrollRequest.scroll(scroll)
+ searchResponse = client.scroll(scrollRequest, RequestOptions.DEFAULT)
+ scrollId = searchResponse.getScrollId
+ searchHits = searchResponse.getHits.getHits
+ lastHitsSize = searchHits.size
+ hitsIterator = searchHits.toIterator
+ lastHitsSize > 0
+ }
+ else {
+ val clearScrollRequest = new ClearScrollRequest
+ clearScrollRequest.addScrollId(scrollId)
+ val clearScrollResponse = client.clearScroll(clearScrollRequest, RequestOptions.DEFAULT)
+ clearScrollResponse.isSucceeded
+ false
+ }
+ }
+
+ override def hasNext: Boolean = hitsIterator.hasNext || doScroll()
+
+ override def next(): Long = {
+ val h = hitsIterator.next()
+ val doc = h.getSourceAsMap.toMap
+ var id: Long = -1
+ if (doc.contains(idName)) {
+ id = doc.get(idName).get.asInstanceOf[Int].toLong
+ }
+ id
+ }
+ }
+
+}
+
+class InElasticSearchPropertyNodeStore(host: String, port: Int, indexName: String, typeName: String,
+ schema: String = "http", scrollSize: Int = 100, scrollContainTimeMinutes: Int = 10) extends CustomPropertyNodeStore {
+ //initialize solr connection
+ val esClient = EsUtil.createClient(host, port, indexName, typeName, schema)
+
+ def deleteNodes(docsToBeDeleted: Iterable[Long]): Unit = {
+ docsToBeDeleted.foreach(node => EsUtil.deleteData(esClient, indexName, typeName, node.toString))
+ }
+
+ def clearAll(): Unit = {
+ EsUtil.clearAllData(esClient, indexName, typeName)
+ }
+
+ def getRecorderSize(): Long = {
+ EsUtil.getAllSize(esClient, indexName, typeName)
+ }
+
+ private def predicate2EsQuery(expr: NFPredicate): QueryBuilder = {
+ expr match {
+ case expr: NFGreaterThan =>
+ val paramValue = expr.value.asInstanceOf[Value].asObject()
+ val paramKey = expr.propName
+ QueryBuilders.rangeQuery(paramKey).gt(paramValue)
+ case expr: NFGreaterThanOrEqual =>
+ val paramValue = expr.value.asInstanceOf[Value].asObject()
+ val paramKey = expr.propName
+ QueryBuilders.rangeQuery(paramKey).gte(paramValue)
+ case expr: NFLessThan =>
+ val paramValue = expr.value.asInstanceOf[Value].asObject()
+ val paramKey = expr.propName
+ QueryBuilders.rangeQuery(paramKey).lt(paramValue)
+ case expr: NFLessThanOrEqual =>
+ val paramValue = expr.value.asInstanceOf[Value].asObject()
+ val paramKey = expr.propName
+ QueryBuilders.rangeQuery(paramKey).lte(paramValue)
+ case expr: NFEquals =>
+ val paramValue = expr.value.asInstanceOf[Value].asObject()
+ val paramKey = expr.propName
+ QueryBuilders.termQuery(paramKey, paramValue)
+ case expr: NFNotEquals =>
+ val paramValue = expr.value.asInstanceOf[Value].asObject()
+ val paramKey = expr.propName
+ QueryBuilders.boolQuery().mustNot(QueryBuilders.termQuery(paramKey, paramValue))
+ case expr: NFNotNull =>
+ val paramKey = expr.propName
+ QueryBuilders.existsQuery(paramKey)
+ case expr: NFIsNull =>
+ val paramKey = expr.propName
+ QueryBuilders.boolQuery().mustNot(QueryBuilders.existsQuery(paramKey))
+ case expr: NFTrue =>
+ QueryBuilders.matchAllQuery()
+ case expr: NFFalse =>
+ QueryBuilders.boolQuery().mustNot(QueryBuilders.matchAllQuery())
+ case expr: NFStartsWith =>
+ val paramValue = expr.text
+ val paramKey = expr.propName
+ QueryBuilders.prefixQuery(paramKey, paramValue)
+ case expr: NFEndsWith =>
+ val paramValue = expr.text
+ val paramKey = expr.propName
+ QueryBuilders.regexpQuery(paramKey + ".keyword", ".*" + paramValue)
+ case expr: NFHasProperty =>
+ val paramKey = expr.propName
+ QueryBuilders.existsQuery(paramKey)
+ case expr: NFContainsWith =>
+ val paramValue = expr.text
+ val paramKey = expr.propName
+ if (paramKey.equals(EsUtil.labelName)) {
+ QueryBuilders.commonTermsQuery(paramKey, paramValue)
+ }
+ else {
+ QueryBuilders.regexpQuery(paramKey + ".keyword", ".*" + paramValue + ".*")
+ }
+ case expr: NFRegexp =>
+ val paramValue = expr.text
+ val paramKey = expr.propName
+ QueryBuilders.regexpQuery(paramKey, paramValue)
+ case expr: NFAnd =>
+ val q1 = predicate2EsQuery(expr.a)
+ val q2 = predicate2EsQuery(expr.b)
+ QueryBuilders.boolQuery().must(q1).must(q2)
+ case expr: NFOr =>
+ val q1 = predicate2EsQuery(expr.a)
+ val q2 = predicate2EsQuery(expr.b)
+ QueryBuilders.boolQuery().should(q1).should(q2)
+ case expr: NFNot =>
+ val q1 = predicate2EsQuery(expr.a)
+ QueryBuilders.boolQuery().mustNot(q1)
+ }
+ }
+
+ private def filterNodesWithProperties(expr: NFPredicate): Iterable[NodeWithProperties] = {
+ val q = predicate2EsQuery(expr)
+ EsUtil.searchWithProperties(esClient, indexName, typeName, q, scrollSize, scrollContainTimeMinutes)
+ }
+
+ override def filterNodes(expr: NFPredicate): Iterable[Long] = {
+ val q = predicate2EsQuery(expr)
+ EsUtil.searchNodeId(esClient, indexName, typeName, q, scrollSize, scrollContainTimeMinutes)
+ }
+
+ override def getNodesByLabel(label: String): Iterable[Long] = {
+ val propName = EsUtil.labelName
+ filterNodes(NFContainsWith(propName, label))
+ }
+
+ override def getNodeBylabelAndFilter(label: String, expr: NFPredicate): Iterable[Long] = {
+ val propName = EsUtil.labelName
+ filterNodes(NFAnd(NFContainsWith(propName, label), expr))
+ }
+
+ override def getNodeById(id: Long): Option[NodeWithProperties] = {
+ val propName = EsUtil.idName
+ filterNodesWithProperties(NFEquals(propName, Values.of(id))).headOption
+ }
+
+ // for tests
+ def filterNodesWithProperties(query: QueryBuilder): Iterable[NodeWithProperties] = {
+ EsUtil.searchWithProperties(esClient, indexName, typeName, query, scrollSize, scrollContainTimeMinutes)
+ }
+
+ // for tests
+ def filterNodes(query: QueryBuilder): Iterable[Long] = {
+ EsUtil.searchNodeId(esClient, indexName, typeName, query, scrollSize, scrollContainTimeMinutes)
+ }
+
+ // for tests
+ def getNodesWithPropertiesByLabel(label: String): Iterable[NodeWithProperties] = {
+ val propName = EsUtil.labelName
+ filterNodesWithProperties(NFContainsWith(propName, label))
+ }
+
+ override def close(ctx: PandaModuleContext): Unit = {
+ esClient.close()
+ }
+
+ override def start(ctx: PandaModuleContext): Unit = {
+ }
+
+ override def beginWriteTransaction(): PropertyWriteTransaction = {
+ new BufferedExternalPropertyWriteTransaction(this,
+ new InEsGroupedOpVisitor(true, esClient, indexName, typeName),
+ new InEsGroupedOpVisitor(false, esClient, indexName, typeName))
+ }
+}
+
+class InEsGroupedOpVisitor(isCommit: Boolean, esClient: RestHighLevelClient, indexName: String, typeName: String)
+ extends GroupedOpVisitor {
+
+ var oldState = mutable.Map[Long, MutableNodeWithProperties]()
+ var newState = mutable.Map[Long, MutableNodeWithProperties]()
+
+ def addNodes(docsToAdded: Iterable[NodeWithProperties]): Unit = {
+ docsToAdded.map { x =>
+ val builder = XContentFactory.jsonBuilder
+ builder.startObject
+ builder.field(EsUtil.idName, x.id)
+ builder.field(EsUtil.labelName, x.labels.toArray[String])
+ x.props.foreach(y => {
+ builder.field(y._1, EsUtil.neo4jValueToScala(y._2))
+ })
+ builder.endObject()
+ EsUtil.addData(esClient, indexName, typeName, x.id.toString, builder)
+ }
+ }
+
+ def getNodeWithPropertiesById(nodeId: Long): NodeWithProperties = {
+ val dataMap = EsUtil.getData(esClient, indexName, typeName, nodeId.toString)
+ EsUtil.sourceMapToNodeWithProperties(dataMap.toMap)
+ }
+
+ def deleteNodes(docsToBeDeleted: Iterable[Long]): Unit = {
+ docsToBeDeleted.foreach(node => EsUtil.deleteData(esClient, indexName, typeName, node.toString))
+ }
+
+ override def start(ops: GroupedOps): Unit = {
+ this.oldState = ops.oldState
+ this.newState = ops.newState
+ }
+
+ override def end(ops: GroupedOps): Unit = {
+
+ }
+
+ override def visitAddNode(nodeId: Long, props: Map[String, Value], labels: Array[String]): Unit = {
+ if (isCommit) addNodes(Iterable(NodeWithProperties(nodeId, props, labels)))
+ else visitDeleteNode(nodeId)
+ }
+
+ override def visitDeleteNode(nodeId: Long): Unit = {
+ if (isCommit) deleteNodes(Iterable(nodeId))
+ else {
+ val oldNode = oldState.get(nodeId).head
+ addNodes(Iterable(NodeWithProperties(nodeId, oldNode.props.toMap, oldNode.labels)))
+ }
+ }
+
+ def getEsNodeById(id: Long): Map[String, Object] = {
+ EsUtil.getData(esClient, indexName, typeName, id.toString).toMap
+ }
+
+ override def visitUpdateNode(nodeId: Long, addedProps: Map[String, Value],
+ updateProps: Map[String, Value], removeProps: Array[String],
+ addedLabels: Array[String], removedLabels: Array[String]): Unit = {
+
+ if (isCommit) {
+ val doc = getEsNodeById(nodeId)
+
+ val node = EsUtil.sourceMapToNodeWithProperties(doc)
+ val mutiNode = node.mutable()
+ mutiNode.props ++= addedProps
+ mutiNode.props ++= updateProps
+ mutiNode.props --= removeProps
+ mutiNode.labels ++= addedLabels
+ mutiNode.labels --= removedLabels
+
+ visitAddNode(nodeId, mutiNode.props.toMap, mutiNode.labels.toArray)
+ }
+
+ else {
+ visitDeleteNode(nodeId)
+ val oldNode = oldState.get(nodeId).head
+ addNodes(Iterable(NodeWithProperties(nodeId, oldNode.props.toMap, oldNode.labels)))
+ }
+
+ }
+
+ override def work(): Unit = {
+ val nodeToAdd = ArrayBuffer[NodeWithProperties]()
+ val nodeToDelete = ArrayBuffer[Long]()
+ if (isCommit) {
+ newState.foreach(tle => nodeToAdd += NodeWithProperties(tle._1, tle._2.props.toMap, tle._2.labels))
+ oldState.foreach(tle => {
+ if (!newState.contains(tle._1)) nodeToDelete += tle._1
+ })
+ }
+ else {
+ oldState.foreach(tle => nodeToAdd += NodeWithProperties(tle._1, tle._2.props.toMap, tle._2.labels))
+ newState.foreach(tle => {
+ if (!oldState.contains(tle._1)) nodeToDelete += tle._1
+ })
+ }
+
+ if (!nodeToAdd.isEmpty) this.addNodes(nodeToAdd)
+ if (!nodeToDelete.isEmpty) this.deleteNodes(nodeToDelete)
+ }
+}
\ No newline at end of file
diff --git a/external-properties/src/main/scala/cn/pandadb/externalprops/InMemoryPropertyNodeStore.scala b/external-properties/src/main/scala/cn/pandadb/externalprops/InMemoryPropertyNodeStore.scala
new file mode 100644
index 00000000..4a8140a9
--- /dev/null
+++ b/external-properties/src/main/scala/cn/pandadb/externalprops/InMemoryPropertyNodeStore.scala
@@ -0,0 +1,228 @@
+package cn.pandadb.externalprops
+
+import cn.pandadb.util.{Configuration, PandaModuleContext}
+import org.neo4j.cypher.internal.runtime.interpreted._
+import org.neo4j.values.AnyValue
+import org.neo4j.values.storable.{NumberValue, StringValue, Value}
+
+import scala.collection.mutable
+import scala.collection.mutable.ArrayBuffer
+
+/**
+ * Created by bluejoe on 2019/10/7.
+ */
+class InMemoryPropertyNodeStoreFactory extends ExternalPropertyStoreFactory {
+ override def create(conf: Configuration): CustomPropertyNodeStore = InMemoryPropertyNodeStore;
+}
+
+/**
+ * used for unit test
+ */
+object InMemoryPropertyNodeStore extends CustomPropertyNodeStore {
+ val nodes = mutable.Map[Long, NodeWithProperties]();
+
+ def firstFilterNodes(expr: NFPredicate): Iterable[NodeWithProperties] = {
+ expr match {
+ case NFGreaterThan(fieldName: String, value: AnyValue) =>
+ nodes.values.filter(x => x.mutable().props.get(fieldName).map(_.asInstanceOf[NumberValue].doubleValue() >
+ value.asInstanceOf[NumberValue].doubleValue()).getOrElse(false))
+
+ /*case NFLessThan(fieldName: String, value: AnyValue) =>
+ nodes.values.filter(x => x.field(fieldName).map(_.asInstanceOf[NumberValue].doubleValue() <
+ value.asInstanceOf[NumberValue].doubleValue()).getOrElse(false))*/
+ case NFLessThan(fieldName: String, value: AnyValue) =>
+ nodes.values.filter(x => x.mutable().props.get(fieldName).map(_.asInstanceOf[NumberValue].doubleValue() <
+ value.asInstanceOf[NumberValue].doubleValue()).getOrElse(false))
+
+ case NFLessThanOrEqual(fieldName: String, value: AnyValue) =>
+ nodes.values.filter(x => x.mutable().props.get(fieldName).map(_.asInstanceOf[NumberValue].doubleValue() <=
+ value.asInstanceOf[NumberValue].doubleValue()).getOrElse(false))
+
+ case NFGreaterThanOrEqual(fieldName: String, value: AnyValue) =>
+ nodes.values.filter(x => x.mutable().props.get(fieldName).map(_.asInstanceOf[NumberValue].doubleValue() >=
+ value.asInstanceOf[NumberValue].doubleValue()).getOrElse(false))
+
+ /* case NFEquals(fieldName: String, value: AnyValue) =>
+ nodes.values.filter(x => x.mutable().props.get(fieldName).map(_.asInstanceOf[NumberValue].doubleValue() ==
+ value.asInstanceOf[NumberValue].doubleValue()).getOrElse(false))*/
+ case NFEquals(fieldName: String, value: AnyValue) =>
+ nodes.values.filter(x => x.mutable().props.get(fieldName).map(_.asObject().toString ==
+ value.asInstanceOf[Value].asObject().toString).getOrElse(false))
+ case NFContainsWith(propName, text) =>
+ nodes.values.filter(x => x.mutable().props.get(propName).map(_.asInstanceOf[StringValue].stringValue().contains(text)
+ ).getOrElse(false))
+ case NFStartsWith(propName, text) =>
+ nodes.values.filter(x => x.mutable().props.get(propName).map(_.asInstanceOf[StringValue].stringValue().startsWith(text)
+ ).getOrElse(false))
+
+ case NFEndsWith(propName, text) =>
+ nodes.values.filter(x => x.mutable().props.get(propName).map(_.asInstanceOf[StringValue].stringValue().endsWith(text)
+ ).getOrElse(false))
+ }
+ }
+
+ def filterNodesWithProperties(expr: NFPredicate): Iterable[NodeWithProperties] = {
+ expr match {
+ case NFAnd(a, b) => filterNodesWithProperties(a).toSet & filterNodesWithProperties(b).toSet
+ case NFNot(a) => nodes.values.toSet -- firstFilterNodes(a)
+ case NFOr(a, b) => filterNodesWithProperties(a).toSet | filterNodesWithProperties(b).toSet
+ case _ => firstFilterNodes(expr)
+ }
+ }
+
+ override def filterNodes(expr: NFPredicate): Iterable[Long] = {
+ filterNodesWithProperties(expr).map(n => n.id)
+ }
+
+ def deleteNodes(docsToBeDeleted: Iterable[Long]): Unit = {
+ nodes --= docsToBeDeleted
+ }
+
+ def addNodes(docsToAdded: Iterable[NodeWithProperties]): Unit = {
+ nodes ++= docsToAdded.map(x => x.id -> x)
+ }
+
+ def updateNodes(nodeId: Long, addedProps: Map[String, Value],
+ updateProps: Map[String, Value], removeProps: Array[String],
+ addedLabels: Array[String], removedLabels: Array[String]): Unit = {
+
+ val n: MutableNodeWithProperties = nodes(nodeId).mutable()
+ if (addedProps != null && addedProps.size > 0) {
+ n.props ++= addedProps
+ }
+ if (updateProps != null && updateProps.size > 0) {
+ n.props ++= updateProps
+ }
+ if (removeProps != null && removeProps.size > 0) {
+ removeProps.foreach(f => n.props -= f)
+ }
+ if (addedLabels != null && addedLabels.size > 0) {
+ n.labels ++= addedLabels
+ // nodes(nodeId).labels = nodes(nodeId).labels.toSet
+ }
+ if (removedLabels != null && removedLabels.size > 0) {
+ //val tmpLabels = nodes(nodeId).labels.toSet
+ n.labels --= removedLabels
+ }
+ deleteNodes(Iterable(nodeId))
+ addNodes(Iterable(NodeWithProperties(nodeId, n.props.toMap, n.labels)))
+
+ }
+
+ def getNodeWithPropertiesBylabelAndFilter(label: String, expr: NFPredicate): Iterable[NodeWithProperties] = {
+ //val propName = SolrUtil.labelName
+ //filterNodes(NFAnd(NFContainsWith(propName, label), expr))
+ getNodesWithPropertiesByLabel(label).toSet & filterNodesWithProperties(expr).toSet
+ }
+
+ override def getNodeBylabelAndFilter(label: String, expr: NFPredicate): Iterable[Long] = {
+ getNodeWithPropertiesBylabelAndFilter(label, expr).map(n => n.id)
+ }
+
+ def getNodesWithPropertiesByLabel(label: String): Iterable[NodeWithProperties] = {
+ val res = mutable.ArrayBuffer[NodeWithProperties]()
+ nodes.map(n => {
+ //println(n)
+ if (n._2.labels.toArray.contains(label)) {
+ res.append(n._2)
+ }
+ })
+ res
+ }
+
+ override def getNodesByLabel(label: String): Iterable[Long] = {
+ getNodesWithPropertiesByLabel(label).map(n => n.id)
+ }
+
+ override def getNodeById(id: Long): Option[NodeWithProperties] = {
+ nodes.get(id)
+ }
+
+ override def start(ctx: PandaModuleContext): Unit = {
+ nodes.clear()
+ }
+
+ override def close(ctx: PandaModuleContext): Unit = {
+ nodes.clear()
+ }
+
+ override def beginWriteTransaction(): PropertyWriteTransaction = {
+ new BufferedExternalPropertyWriteTransaction(this, new InMemoryGroupedOpVisitor(true, nodes), new InMemoryGroupedOpVisitor(false, nodes))
+ }
+}
+
+class InMemoryGroupedOpVisitor(isCommit: Boolean, nodes: mutable.Map[Long, NodeWithProperties]) extends GroupedOpVisitor {
+
+ var oldState = mutable.Map[Long, MutableNodeWithProperties]();
+ var newState = mutable.Map[Long, MutableNodeWithProperties]();
+
+ override def start(ops: GroupedOps): Unit = {
+
+ this.oldState = ops.oldState
+ this.newState = ops.newState
+
+ }
+
+ override def end(ops: GroupedOps): Unit = {
+
+ //this.oldState.clear()
+ //this.newState.clear()
+
+ }
+
+ override def visitAddNode(nodeId: Long, props: Map[String, Value], labels: Array[String]): Unit = {
+ if (isCommit) InMemoryPropertyNodeStore.addNodes(Iterable(NodeWithProperties(nodeId, props, labels)))
+ else {
+ InMemoryPropertyNodeStore.deleteNodes(Iterable(nodeId))
+ }
+
+ }
+
+ override def visitDeleteNode(nodeId: Long): Unit = {
+ if (isCommit) InMemoryPropertyNodeStore.deleteNodes(Iterable(nodeId))
+ else {
+
+ val oldNode = oldState.get(nodeId).head
+ InMemoryPropertyNodeStore.addNodes(Iterable(NodeWithProperties(nodeId, oldNode.props.toMap, oldNode.labels)))
+
+ }
+ }
+
+ override def visitUpdateNode(nodeId: Long, addedProps: Map[String, Value],
+ updateProps: Map[String, Value], removeProps: Array[String],
+ addedLabels: Array[String], removedLabels: Array[String]): Unit = {
+ if (isCommit) InMemoryPropertyNodeStore.updateNodes(nodeId: Long, addedProps: Map[String, Value],
+ updateProps: Map[String, Value], removeProps: Array[String],
+ addedLabels: Array[String], removedLabels: Array[String])
+ else {
+
+ val oldNode = oldState.get(nodeId).head
+ InMemoryPropertyNodeStore.addNodes(Iterable(NodeWithProperties(nodeId, oldNode.props.toMap, oldNode.labels)))
+
+ }
+ }
+
+ override def work(): Unit = {
+
+ val nodeToAdd = ArrayBuffer[NodeWithProperties]()
+ val nodeToDelete = ArrayBuffer[Long]()
+ if (isCommit) {
+
+ newState.foreach(tle => nodeToAdd += NodeWithProperties(tle._1, tle._2.props.toMap, tle._2.labels))
+ oldState.foreach(tle => {
+ if (!newState.contains(tle._1)) nodeToDelete += tle._1
+ })
+ }
+ else {
+
+ oldState.foreach(tle => nodeToAdd += NodeWithProperties(tle._1, tle._2.props.toMap, tle._2.labels))
+ newState.foreach(tle => {
+ if (!oldState.contains(tle._1)) nodeToDelete += tle._1
+ })
+ }
+
+ InMemoryPropertyNodeStore.addNodes(nodeToAdd)
+ InMemoryPropertyNodeStore.deleteNodes(nodeToDelete)
+
+ }
+}
\ No newline at end of file
diff --git a/external-properties/src/main/scala/cn/pandadb/externalprops/InSolrPropertyNodeStore.scala b/external-properties/src/main/scala/cn/pandadb/externalprops/InSolrPropertyNodeStore.scala
new file mode 100644
index 00000000..b3f1216f
--- /dev/null
+++ b/external-properties/src/main/scala/cn/pandadb/externalprops/InSolrPropertyNodeStore.scala
@@ -0,0 +1,379 @@
+package cn.pandadb.externalprops
+
+import java.util
+
+import cn.pandadb.util.ConfigUtils._
+import cn.pandadb.util.{Configuration, PandaModuleContext}
+import org.apache.solr.client.solrj.SolrQuery
+import org.apache.solr.client.solrj.impl.CloudSolrClient
+import org.apache.solr.common.{SolrDocument, SolrInputDocument}
+import org.neo4j.cypher.internal.runtime.interpreted.{NFLessThan, NFPredicate, _}
+import org.neo4j.values.storable.{ArrayValue, Value, Values}
+
+import scala.collection.JavaConversions._
+import scala.collection.mutable
+import scala.collection.mutable.ArrayBuffer
+
+
+class InSolrPropertyNodeStoreFactory extends ExternalPropertyStoreFactory {
+ override def create(conf: Configuration): CustomPropertyNodeStore = {
+
+ import cn.pandadb.util.ConfigUtils._
+
+ val zkAddr = conf.getRequiredValueAsString("external.properties.store.solr.zk")
+ val zkCollection = conf.getRequiredValueAsString("external.properties.store.solr.collection")
+ new InSolrPropertyNodeStore(zkAddr, zkCollection)
+ }
+}
+
+object SolrUtil {
+ val idName = "id"
+ val labelName = "labels"
+ val tik = "id,labels,_version_"
+ val arrayName = "Array"
+ val dateType = "time"
+ val maxRows = 50000000
+
+ def solrDoc2nodeWithProperties(doc: SolrDocument): NodeWithProperties = {
+ val props = mutable.Map[String, Value]()
+ val id = doc.get(idName)
+ val labels = ArrayBuffer[String]()
+ if (doc.get(labelName) != null) doc.get(labelName).asInstanceOf[util.ArrayList[String]].foreach(u => labels += u)
+ val fieldsName = doc.getFieldNames
+ fieldsName.foreach(y => {
+ if (tik.indexOf(y) < 0) {
+ if (doc.get(y).getClass.getName.contains(arrayName)) {
+ val tempArray = ArrayBuffer[AnyRef]()
+ doc.get(y).asInstanceOf[util.ArrayList[AnyRef]].foreach(u => tempArray += u)
+ if (tempArray.size <= 1) props += y -> Values.of(tempArray.head)
+ else props += y -> getValueFromArray(tempArray)
+ }
+ else props += y -> Values.of(doc.get(y))
+ }
+ })
+ NodeWithProperties(id.toString.toLong, props.toMap, labels)
+ }
+
+ def getValueFromArray(value: ArrayBuffer[AnyRef]): Value = {
+ val typeObj = value.head
+ typeObj match {
+ case s1: java.lang.String =>
+ val strArr = value.map(_.toString).toArray
+ val result = Values.stringArray(strArr: _*)
+ result
+ case s2: java.lang.Boolean =>
+ Values.booleanArray(value.map(_.asInstanceOf[Boolean]).toArray)
+ case s3: java.lang.Long =>
+ Values.longArray(value.map(_.asInstanceOf[Long]).toArray)
+ case s4: java.lang.Byte =>
+ Values.byteArray(value.map(_.asInstanceOf[Byte]).toArray)
+ case s5: java.lang.Short =>
+ Values.shortArray(value.map(_.asInstanceOf[Short]).toArray)
+ case s6: java.lang.Integer =>
+ Values.intArray(value.map(_.asInstanceOf[Int]).toArray)
+ case s7: java.lang.Double =>
+ Values.doubleArray(value.map(_.asInstanceOf[Double]).toArray)
+ case s8: java.lang.Float =>
+ Values.floatArray(value.map(_.asInstanceOf[Float]).toArray)
+ case _ => null
+ }
+ }
+
+}
+
+/**
+ * Created by bluejoe on 2019/10/7.
+ */
+class InSolrPropertyNodeStore(zkUrl: String, collectionName: String) extends CustomPropertyNodeStore {
+ //initialize solr connection
+ val _solrClient = {
+ val client = new CloudSolrClient(zkUrl);
+ client.setZkClientTimeout(30000);
+ client.setZkConnectTimeout(50000);
+ client.setDefaultCollection(collectionName);
+ client
+ }
+
+ def deleteNodes(docsToBeDeleted: Iterable[Long]): Unit = {
+ _solrClient.deleteById(docsToBeDeleted.map(_.toString).toList);
+ _solrClient.commit();
+ }
+
+ def clearAll(): Unit = {
+ _solrClient.deleteByQuery("*:*")
+ _solrClient.commit()
+ }
+
+ def getRecorderSize: Int = {
+ val query = "*:*"
+ _solrClient.query(new SolrQuery().setQuery(query)).getResults().getNumFound.toInt
+ }
+
+ def addNodes(docsToAdded: Iterable[NodeWithProperties]): Unit = {
+
+ _solrClient.add(docsToAdded.map { x =>
+ val doc = new SolrInputDocument();
+ x.props.foreach(y => {
+ if (Values.isArrayValue(y._2)) {
+ y._2.asInstanceOf[ArrayValue].foreach(u => doc.addField(y._1, u.asInstanceOf[Value].asObject()))
+ }
+ else doc.addField(y._1, y._2.asObject())
+ });
+ doc.addField(SolrUtil.idName, x.id);
+ x.labels.foreach(label => doc.addField(SolrUtil.labelName, label))
+ doc
+ })
+
+ _solrClient.commit();
+
+ }
+
+ private def predicate2SolrQuery(expr: NFPredicate): String = {
+ var q: Option[String] = None
+ expr match {
+ case expr: NFGreaterThan =>
+ val paramValue = expr.value.asInstanceOf[Value].asObject()
+ val paramKey = expr.propName
+ q = Some(s"$paramKey:{ $paramValue TO * }")
+ case expr: NFGreaterThanOrEqual =>
+ val paramValue = expr.value.asInstanceOf[Value].asObject()
+ val paramKey = expr.propName
+ q = Some(s"$paramKey:[ $paramValue TO * ]")
+ case expr: NFLessThan =>
+ val paramValue = expr.value.asInstanceOf[Value].asObject()
+ val paramKey = expr.propName
+ q = Some(s"$paramKey:{ * TO $paramValue}")
+ case expr: NFLessThanOrEqual =>
+ val paramValue = expr.value.asInstanceOf[Value].asObject()
+ val paramKey = expr.propName
+ q = Some(s"$paramKey:[ * TO $paramValue]")
+ case expr: NFEquals =>
+ val paramValue = expr.value.asInstanceOf[Value].asObject()
+ val paramKey = expr.propName
+ q = Some(s"$paramKey:$paramValue")
+ case expr: NFNotEquals =>
+ val paramValue = expr.value.asInstanceOf[Value].asObject()
+ val paramKey = expr.propName
+ q = Some(s"-$paramKey:$paramValue")
+ case expr: NFNotNull =>
+ val paramKey = expr.propName
+ q = Some(s"$paramKey:*")
+ case expr: NFIsNull =>
+ val paramKey = expr.propName
+ q = Some(s"-$paramKey:*")
+ case expr: NFTrue =>
+ q = Some(s"*:*")
+ case expr: NFFalse =>
+ q = Some(s"-*:*")
+ case expr: NFStartsWith =>
+ val paramValue = expr.text
+ val paramKey = expr.propName
+ q = Some(s"$paramKey:$paramValue*")
+ case expr: NFEndsWith =>
+ val paramValue = expr.text
+ val paramKey = expr.propName
+ q = Some(s"$paramKey:*$paramValue")
+ case expr: NFHasProperty =>
+ val paramKey = expr.propName
+ q = Some(s"$paramKey:[* TO *]")
+ case expr: NFContainsWith =>
+ val paramValue = expr.text
+ val paramKey = expr.propName
+ q = Some(s"$paramKey:*$paramValue*")
+ case expr: NFRegexp =>
+ val paramValue = expr.text.replace(".", "")
+ val paramKey = expr.propName
+ q = Some(s"$paramKey:$paramValue")
+ case expr: NFAnd =>
+ val q1 = predicate2SolrQuery(expr.a)
+ val q2 = predicate2SolrQuery(expr.b)
+ q = Some(s"($q1 && $q2)")
+ case expr: NFOr =>
+ val q1 = predicate2SolrQuery(expr.a)
+ val q2 = predicate2SolrQuery(expr.b)
+ q = Some(s"($q1 || $q2)")
+ case expr: NFNot =>
+ val q1 = predicate2SolrQuery(expr.a)
+ q = if (q1.indexOf("-") >= 0) Some(s"${q1.substring(q1.indexOf("-") + 1)}") else Some(s"-$q1")
+ case _ => q = None
+ }
+ q.get
+ }
+
+ def filterNodesWithProperties(expr: NFPredicate): Iterable[NodeWithProperties] = {
+
+ var q: Option[String] = None;
+ expr match {
+ case expr: NFAnd =>
+ val q1 = predicate2SolrQuery(expr.a)
+ val q2 = predicate2SolrQuery(expr.b)
+ q = Some(s"($q1 && $q2)")
+ case expr: NFOr =>
+ val q1 = predicate2SolrQuery(expr.a)
+ val q2 = predicate2SolrQuery(expr.b)
+ q = Some(s"($q1 || $q2)")
+
+ case expr: NFNot =>
+ val q1 = predicate2SolrQuery(expr.a)
+ q = if (q1.indexOf("-") >= 0) Some(s"${q1.substring(q1.indexOf("-") + 1)}") else Some(s"-$q1")
+
+ case _ =>
+ val q1 = predicate2SolrQuery(expr)
+ q = Some(s"$q1")
+
+ }
+ val query = new SolrQuery()
+ query.setQuery(q.get)
+ val res = new SolrQueryResults(_solrClient, query, 10000)
+ res.iterator2().toIterable
+ }
+
+ override def filterNodes(expr: NFPredicate): Iterable[Long] = {
+ filterNodesWithProperties(expr).map(n => n.id)
+ }
+
+ def getNodesWithPropertiesByLabel(label: String): Iterable[NodeWithProperties] = {
+ val propName = SolrUtil.labelName
+ filterNodesWithProperties(NFContainsWith(propName, label))
+ }
+
+ override def getNodesByLabel(label: String): Iterable[Long] = {
+ getNodesWithPropertiesByLabel(label).map(n => n.id)
+ }
+
+ def getNodeWithPropertiesBylabelAndFilter(label: String, expr: NFPredicate): Iterable[NodeWithProperties] = {
+ val propName = SolrUtil.labelName
+ filterNodesWithProperties(NFAnd(NFContainsWith(propName, label), expr))
+ }
+
+ override def getNodeBylabelAndFilter(label: String, expr: NFPredicate): Iterable[Long] = {
+ getNodeWithPropertiesBylabelAndFilter(label, expr).map(n => n.id)
+ }
+
+ override def getNodeById(id: Long): Option[NodeWithProperties] = {
+ val propName = SolrUtil.idName
+ filterNodesWithProperties(NFEquals(propName, Values.of(id))).headOption
+ }
+
+ override def close(ctx: PandaModuleContext): Unit = {
+ //_solrClient.close()
+ }
+
+ override def start(ctx: PandaModuleContext): Unit = {
+ }
+
+ override def beginWriteTransaction(): PropertyWriteTransaction = {
+ new BufferedExternalPropertyWriteTransaction(this, new InSolrGroupedOpVisitor(true, _solrClient), new InSolrGroupedOpVisitor(false, _solrClient))
+ }
+}
+
+class InSolrGroupedOpVisitor(isCommit: Boolean, _solrClient: CloudSolrClient) extends GroupedOpVisitor {
+
+ var oldState = mutable.Map[Long, MutableNodeWithProperties]();
+ var newState = mutable.Map[Long, MutableNodeWithProperties]();
+
+ def addNodes(docsToAdded: Iterable[NodeWithProperties]): Unit = {
+
+ _solrClient.add(docsToAdded.map { x =>
+ val doc = new SolrInputDocument();
+ x.props.foreach(y => {
+ if (Values.isArrayValue(y._2)) {
+ y._2.asInstanceOf[ArrayValue].foreach(u => doc.addField(y._1, u.asInstanceOf[Value].asObject()))
+ }
+ else doc.addField(y._1, y._2.asObject())
+ });
+ doc.addField(SolrUtil.idName, x.id);
+ x.labels.foreach(label => doc.addField(SolrUtil.labelName, label))
+ doc
+ })
+ _solrClient.commit();
+
+ }
+
+ def getNodeWithPropertiesById(nodeId: Long): NodeWithProperties = {
+ val doc = _solrClient.getById(nodeId.toString)
+ SolrUtil.solrDoc2nodeWithProperties(doc)
+ }
+
+ def deleteNodes(docsToBeDeleted: Iterable[Long]): Unit = {
+ _solrClient.deleteById(docsToBeDeleted.map(_.toString).toList);
+ _solrClient.commit();
+ }
+
+ override def start(ops: GroupedOps): Unit = {
+
+ this.oldState = ops.oldState
+ this.newState = ops.newState
+
+ }
+
+ override def end(ops: GroupedOps): Unit = {
+
+ }
+
+ override def visitAddNode(nodeId: Long, props: Map[String, Value], labels: Array[String]): Unit = {
+ if (isCommit) addNodes(Iterable(NodeWithProperties(nodeId, props, labels)))
+ else visitDeleteNode(nodeId)
+ }
+
+ override def visitDeleteNode(nodeId: Long): Unit = {
+ if (isCommit) deleteNodes(Iterable(nodeId))
+ else {
+ val oldNode = oldState.get(nodeId).head
+ addNodes(Iterable(NodeWithProperties(nodeId, oldNode.props.toMap, oldNode.labels)))
+ }
+ }
+
+ def getSolrNodeById(id: Long): SolrDocument = {
+ _solrClient.getById(id.toString)
+ }
+
+ override def visitUpdateNode(nodeId: Long, addedProps: Map[String, Value],
+ updateProps: Map[String, Value], removeProps: Array[String],
+ addedLabels: Array[String], removedLabels: Array[String]): Unit = {
+
+ if (isCommit) {
+
+ val doc = getSolrNodeById(nodeId)
+
+ val node = SolrUtil.solrDoc2nodeWithProperties(doc)
+ val mutiNode = node.mutable()
+ mutiNode.props ++= addedProps
+ mutiNode.props ++= updateProps
+ mutiNode.props --= removeProps
+ mutiNode.labels ++= addedLabels
+ mutiNode.labels --= removedLabels
+
+ visitAddNode(nodeId, mutiNode.props.toMap, mutiNode.labels.toArray)
+ }
+
+ else {
+ visitDeleteNode(nodeId)
+ val oldNode = oldState.get(nodeId).head
+ addNodes(Iterable(NodeWithProperties(nodeId, oldNode.props.toMap, oldNode.labels)))
+ }
+
+ }
+
+ override def work(): Unit = {
+ val nodeToAdd = ArrayBuffer[NodeWithProperties]()
+ val nodeToDelete = ArrayBuffer[Long]()
+ if (isCommit) {
+
+ newState.foreach(tle => nodeToAdd += NodeWithProperties(tle._1, tle._2.props.toMap, tle._2.labels))
+ oldState.foreach(tle => {
+ if (!newState.contains(tle._1)) nodeToDelete += tle._1
+ })
+ }
+ else {
+
+ oldState.foreach(tle => nodeToAdd += NodeWithProperties(tle._1, tle._2.props.toMap, tle._2.labels))
+ newState.foreach(tle => {
+ if (!oldState.contains(tle._1)) nodeToDelete += tle._1
+ })
+ }
+
+ if (!nodeToAdd.isEmpty) this.addNodes(nodeToAdd)
+ if (!nodeToDelete.isEmpty) this.deleteNodes(nodeToDelete)
+ }
+}
\ No newline at end of file
diff --git a/external-properties/src/main/scala/cn/pandadb/externalprops/SolrIterable.scala b/external-properties/src/main/scala/cn/pandadb/externalprops/SolrIterable.scala
new file mode 100644
index 00000000..a54645de
--- /dev/null
+++ b/external-properties/src/main/scala/cn/pandadb/externalprops/SolrIterable.scala
@@ -0,0 +1,147 @@
+package cn.pandadb.externalprops
+
+import org.apache.solr.client.solrj.SolrQuery
+import org.apache.solr.client.solrj.impl.CloudSolrClient
+import org.apache.solr.common.SolrDocument
+import org.apache.log4j.Logger
+import org.apache.solr.client.solrj.SolrQuery.ORDER
+import org.apache.solr.common.params.CursorMarkParams
+
+import scala.collection.JavaConversions.asScalaBuffer
+import scala.collection.JavaConversions.bufferAsJavaList
+import scala.collection.JavaConversions.mapAsJavaMap
+import scala.collection.JavaConversions.seqAsJavaList
+import scala.collection.immutable.Map
+import scala.collection.mutable.ArrayBuffer
+
+class SolrQueryResults(_solrClient: CloudSolrClient, solrQuery: SolrQuery, pageSize: Int = 20) {
+
+ def iterator(): SolrQueryResultsIterator = new SolrQueryResultsIterator(_solrClient, solrQuery, pageSize)
+
+ def getAllResults(): Iterable[NodeWithProperties] = {
+ val nodeArray = ArrayBuffer[NodeWithProperties]()
+ solrQuery.setRows(SolrUtil.maxRows)
+ val res = _solrClient.query(solrQuery).getResults
+ res.foreach(
+ x => {
+ nodeArray += SolrUtil.solrDoc2nodeWithProperties(x)
+ }
+ )
+ nodeArray
+ }
+
+ def iterator2(): SolrQueryResultsCursorIterator = new SolrQueryResultsCursorIterator(_solrClient, solrQuery, pageSize)
+}
+
+class SolrQueryResultsCursorIterator(_solrClient: CloudSolrClient, solrQuery: SolrQuery, pageSize: Int = 20)
+ extends Iterator[NodeWithProperties] {
+
+ var startOfCurrentPage = 0;
+ var rowIteratorWithinCurrentPage: java.util.Iterator[NodeWithProperties] = null;
+ var isFinished = false;
+ val mySolrQuery = solrQuery.getCopy();
+ var cursorMark = CursorMarkParams.CURSOR_MARK_START
+ var nextCursorMark: String = null
+ private var currentData : Iterable[NodeWithProperties] = _
+ mySolrQuery.setRows(pageSize)
+ mySolrQuery.setSort(SolrUtil.idName, ORDER.asc)
+ readNextPage();
+
+ def doc2Node(doc : SolrDocument): NodeWithProperties = {
+ SolrUtil.solrDoc2nodeWithProperties(doc)
+ }
+
+ def readNextPage(): Boolean = {
+
+ mySolrQuery.set(CursorMarkParams.CURSOR_MARK_PARAM, cursorMark)
+ val rsp = _solrClient.query(mySolrQuery)
+ nextCursorMark = rsp.getNextCursorMark
+ val docs = rsp.getResults()
+ val rows = docs.map {doc2Node}
+ currentData = null
+ currentData = rows
+ rowIteratorWithinCurrentPage = rows.iterator()
+ if (cursorMark.equals(nextCursorMark)) {
+ isFinished = true
+ false
+ }
+ else {
+ cursorMark = nextCursorMark
+ true
+ }
+
+ }
+
+ def hasNext(): Boolean = {
+ rowIteratorWithinCurrentPage.hasNext() || readNextPage()
+ }
+
+ def next(): NodeWithProperties = {
+
+ rowIteratorWithinCurrentPage.next()
+
+ }
+
+ def getCurrentData(): Iterable[NodeWithProperties] = {
+ this.currentData
+ }
+
+}
+
+class SolrQueryResultsIterator(_solrClient: CloudSolrClient, solrQuery: SolrQuery, pageSize: Int = 20)
+ extends Iterator[NodeWithProperties] {
+
+ var startOfCurrentPage = 0;
+ var rowIteratorWithinCurrentPage: java.util.Iterator[NodeWithProperties] = null;
+ var totalCountOfRows = -1L;
+ val mySolrQuery = solrQuery.getCopy();
+ var done = true
+ private var currentData : Iterable[NodeWithProperties] = _
+ readNextPage();
+
+ def doc2Node(doc : SolrDocument): NodeWithProperties = {
+ SolrUtil.solrDoc2nodeWithProperties(doc)
+ }
+
+ def readNextPage(): Boolean = {
+
+ if (totalCountOfRows < 0 || startOfCurrentPage < totalCountOfRows) {
+ mySolrQuery.set("start", startOfCurrentPage);
+ mySolrQuery.set("rows", pageSize);
+ startOfCurrentPage += pageSize;
+ //logger.debug(s"executing solr query: $mySolrQuery");
+ val rsp = _solrClient.query(mySolrQuery);
+ val docs = rsp.getResults();
+ totalCountOfRows = docs.getNumFound();
+ //logger.debug(s"numFound: $totalCountOfRows");
+ val rows = docs.map {doc2Node};
+ currentData = null
+ currentData = rows
+ rowIteratorWithinCurrentPage = rows.iterator();
+ true;
+ }
+ else {
+ false;
+ }
+ }
+
+ def hasNext(): Boolean = {
+ rowIteratorWithinCurrentPage.hasNext() || startOfCurrentPage < totalCountOfRows
+ }
+
+ def next(): NodeWithProperties = {
+
+ if (!rowIteratorWithinCurrentPage.hasNext()) {
+
+ if (!readNextPage()) throw new NoSuchElementException();
+
+ }
+ rowIteratorWithinCurrentPage.next()
+
+ }
+
+ def getCurrentData(): Iterable[NodeWithProperties] = {
+ this.currentData
+ }
+
+}
\ No newline at end of file
diff --git a/external-properties/src/main/scala/cn/pandadb/externalprops/module.scala b/external-properties/src/main/scala/cn/pandadb/externalprops/module.scala
new file mode 100644
index 00000000..ee3f48c1
--- /dev/null
+++ b/external-properties/src/main/scala/cn/pandadb/externalprops/module.scala
@@ -0,0 +1,34 @@
+package cn.pandadb.externalprops
+
+import cn.pandadb.util._
+
+class ExternalPropertiesModule extends PandaModule {
+ override def init(ctx: PandaModuleContext): Unit = {
+ val conf = ctx.configuration;
+ import cn.pandadb.util.ConfigUtils._
+
+ val isExternalPropertyStorageEnabled = conf.getValueAsBoolean("external.property.storage.enabled", false)
+ if (isExternalPropertyStorageEnabled) {
+ val factoryClassName = conf.getRequiredValueAsString("external.properties.store.factory")
+
+ val store = Class.forName(factoryClassName).newInstance().asInstanceOf[ExternalPropertyStoreFactory].create(conf)
+ ExternalPropertiesContext.bindCustomPropertyNodeStore(store);
+ }
+ }
+
+ override def close(ctx: PandaModuleContext): Unit = {
+ ExternalPropertiesContext.maybeCustomPropertyNodeStore.foreach(_.start(ctx))
+ }
+
+ override def start(ctx: PandaModuleContext): Unit = {
+ ExternalPropertiesContext.maybeCustomPropertyNodeStore.foreach(_.close(ctx))
+ }
+}
+
+object ExternalPropertiesContext extends ContextMap {
+ def maybeCustomPropertyNodeStore: Option[CustomPropertyNodeStore] = getOption[CustomPropertyNodeStore]
+
+ def bindCustomPropertyNodeStore(store: CustomPropertyNodeStore): Unit = put[CustomPropertyNodeStore](store);
+
+ def isExternalPropStorageEnabled: Boolean = maybeCustomPropertyNodeStore.isDefined
+}
\ No newline at end of file
diff --git a/external-properties/src/main/scala/org/neo4j/cypher/internal/compiler/v3_5/planner/logical/QueryPlannerConfiguration.scala b/external-properties/src/main/scala/org/neo4j/cypher/internal/compiler/v3_5/planner/logical/QueryPlannerConfiguration.scala
new file mode 100644
index 00000000..eb311288
--- /dev/null
+++ b/external-properties/src/main/scala/org/neo4j/cypher/internal/compiler/v3_5/planner/logical/QueryPlannerConfiguration.scala
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2002-2019 "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.cypher.internal.compiler.v3_5.planner.logical
+
+import cn.pandadb.externalprops.{ExternalPropertiesContext, CustomPropertyNodeStore}
+import org.neo4j.cypher.internal.compiler.v3_5.planner.logical.steps._
+import org.neo4j.cypher.internal.compiler.v3_5.{UpdateStrategy, defaultUpdateStrategy}
+import org.neo4j.cypher.internal.ir.v3_5.{InterestingOrder, QueryGraph}
+import org.neo4j.cypher.internal.v3_5.logical.plans.LogicalPlan
+
+object QueryPlannerConfiguration {
+
+ val bypassIndex = ExternalPropertiesContext.isExternalPropStorageEnabled
+
+ private val noIndexleafPlanFromExpressions: IndexedSeq[LeafPlanner with LeafPlanFromExpressions] = IndexedSeq(
+ // MATCH (n) WHERE id(n) IN ... RETURN n
+ idSeekLeafPlanner,
+
+ // MATCH (n:Person) RETURN n
+ labelScanLeafPlanner
+ )
+
+ private val leafPlanFromExpressions: IndexedSeq[LeafPlanner with LeafPlanFromExpressions] = IndexedSeq(
+ // MATCH (n) WHERE id(n) IN ... RETURN n
+ idSeekLeafPlanner,
+
+ // MATCH (n) WHERE n.prop IN ... RETURN n
+ indexSeekLeafPlanner,
+
+ // MATCH (n) WHERE has(n.prop) RETURN n
+ // MATCH (n:Person) WHERE n.prop CONTAINS ...
+ indexScanLeafPlanner,
+
+ // MATCH (n:Person) RETURN n
+ labelScanLeafPlanner
+ )
+
+ private val leafPlanFromExpressionsSwitch = if (bypassIndex) noIndexleafPlanFromExpressions else leafPlanFromExpressions
+
+ val allLeafPlanners = leafPlanFromExpressionsSwitch ++ IndexedSeq(
+ argumentLeafPlanner,
+
+ // MATCH (n) RETURN n
+ allNodesLeafPlanner,
+
+ // Handles OR between other leaf planners
+ OrLeafPlanner(leafPlanFromExpressions))
+
+ val default: QueryPlannerConfiguration = {
+ val predicateSelector = Selector(pickBestPlanUsingHintsAndCost,
+ selectPatternPredicates,
+ triadicSelectionFinder,
+ selectCovered,
+ selectHasLabelWithJoin
+ )
+
+ QueryPlannerConfiguration(
+ pickBestCandidate = pickBestPlanUsingHintsAndCost,
+ applySelections = predicateSelector,
+ optionalSolvers = Seq(
+ applyOptional,
+ leftOuterHashJoin,
+ rightOuterHashJoin
+ ),
+ leafPlanners = LeafPlannerList(allLeafPlanners),
+ updateStrategy = defaultUpdateStrategy
+ )
+
+ }
+}
+
+case class QueryPlannerConfiguration(leafPlanners: LeafPlannerIterable,
+ applySelections: PlanSelector,
+ optionalSolvers: Seq[OptionalSolver],
+ pickBestCandidate: CandidateSelectorFactory,
+ updateStrategy: UpdateStrategy) {
+
+ def toKit(interestingOrder: InterestingOrder, context: LogicalPlanningContext) =
+ QueryPlannerKit(
+ select = (plan: LogicalPlan, qg: QueryGraph) => applySelections(plan, qg, interestingOrder, context),
+ pickBest = pickBestCandidate(context)
+ )
+
+ def withLeafPlanners(leafPlanners: LeafPlannerIterable): QueryPlannerConfiguration = copy(leafPlanners = leafPlanners)
+
+ def withUpdateStrategy(updateStrategy: UpdateStrategy): QueryPlannerConfiguration = copy(updateStrategy = updateStrategy)
+}
+
+case class QueryPlannerKit(select: (LogicalPlan, QueryGraph) => LogicalPlan, pickBest: CandidateSelector) {
+ def select(plans: Iterable[Seq[LogicalPlan]], qg: QueryGraph): Iterable[Seq[LogicalPlan]] =
+ plans.map(_.map(plan => select(plan, qg)))
+}
\ No newline at end of file
diff --git a/external-properties/src/main/scala/org/neo4j/cypher/internal/compiler/v3_5/planner/logical/steps/verifyBestPlan.scala b/external-properties/src/main/scala/org/neo4j/cypher/internal/compiler/v3_5/planner/logical/steps/verifyBestPlan.scala
new file mode 100644
index 00000000..a1ad4601
--- /dev/null
+++ b/external-properties/src/main/scala/org/neo4j/cypher/internal/compiler/v3_5/planner/logical/steps/verifyBestPlan.scala
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2002-2019 "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.cypher.internal.compiler.v3_5.planner.logical.steps
+
+import cn.pandadb.externalprops.{ExternalPropertiesContext, CustomPropertyNodeStore}
+import org.neo4j.cypher.internal.compiler.v3_5.planner.logical.{LogicalPlanningContext, PlanTransformer}
+import org.neo4j.cypher.internal.compiler.v3_5.{IndexHintUnfulfillableNotification, JoinHintUnfulfillableNotification}
+import org.neo4j.cypher.internal.ir.v3_5.PlannerQuery
+import org.neo4j.cypher.internal.planner.v3_5.spi.PlanContext
+import org.neo4j.cypher.internal.v3_5.logical.plans.LogicalPlan
+import org.neo4j.cypher.internal.v3_5.ast._
+import org.neo4j.cypher.internal.v3_5.expressions.LabelName
+import org.neo4j.cypher.internal.v3_5.util._
+
+object verifyBestPlan extends PlanTransformer {
+
+ val bypassIndex = ExternalPropertiesContext.isExternalPropStorageEnabled
+
+ def apply(plan: LogicalPlan, expected: PlannerQuery, context: LogicalPlanningContext): LogicalPlan = {
+ val constructed = context.planningAttributes.solveds.get(plan.id)
+ if (expected != constructed) {
+ val unfulfillableIndexHints = findUnfulfillableIndexHints(expected, context.planContext)
+ val unfulfillableJoinHints = findUnfulfillableJoinHints(expected, context.planContext)
+ val expectedWithoutHints = expected.withoutHints(unfulfillableIndexHints ++ unfulfillableJoinHints)
+ if (expectedWithoutHints != constructed) {
+ val a: PlannerQuery = expected.withoutHints(expected.allHints)
+ val b: PlannerQuery = constructed.withoutHints(constructed.allHints)
+ if (a != b) {
+ // unknown planner issue failed to find plan (without regard for differences in hints)
+ throw new InternalException(s"Expected \n$expected \n\n\nInstead, got: \n$constructed\nPlan: $plan")
+ } else {
+ // unknown planner issue failed to find plan matching hints (i.e. "implicit hints")
+ val expectedHints = expected.allHints
+ val actualHints = constructed.allHints
+ val missing = expectedHints.diff(actualHints)
+ val solvedInAddition = actualHints.diff(expectedHints)
+ val inventedHintsAndThenSolvedThem = solvedInAddition.exists(!expectedHints.contains(_))
+ if ( !bypassIndex && (missing.nonEmpty || inventedHintsAndThenSolvedThem)) {
+ def out(h: Seq[Hint]) = h.mkString("`", ", ", "`")
+ val details = if (missing.isEmpty)
+ s"""Expected:
+ |${out(expectedHints)}
+ |
+ |Instead, got:
+ |${out(actualHints)}""".stripMargin
+ else
+ s"Could not solve these hints: ${out(missing)}"
+
+ val message =
+ s"""Failed to fulfil the hints of the query.
+ |$details
+ |
+ |Plan $plan""".stripMargin
+
+ throw new HintException(message)
+ }
+ }
+ } else {
+ processUnfulfilledIndexHints(context, unfulfillableIndexHints)
+ processUnfulfilledJoinHints(plan, context, unfulfillableJoinHints)
+ }
+ }
+ plan
+ }
+
+ private def processUnfulfilledIndexHints(context: LogicalPlanningContext, hints: Seq[UsingIndexHint]): Unit = {
+ if (hints.nonEmpty) {
+ // hints referred to non-existent indexes ("explicit hints")
+ if (context.useErrorsOverWarnings) {
+ val firstIndexHint = hints.head
+ throw new IndexHintException(firstIndexHint.variable.name, firstIndexHint.label.name, firstIndexHint.properties.map(_.name), "No such index")
+ } else {
+ hints.foreach { hint =>
+ context.notificationLogger.log(IndexHintUnfulfillableNotification(hint.label.name, hint.properties.map(_.name)))
+ }
+ }
+ }
+ }
+
+ private def processUnfulfilledJoinHints(plan: LogicalPlan, context: LogicalPlanningContext, hints: Seq[UsingJoinHint]): Unit = {
+ if (hints.nonEmpty) {
+ // we were unable to plan hash join on some requested nodes
+ if (context.useErrorsOverWarnings) {
+ val firstJoinHint = hints.head
+ throw new JoinHintException(firstJoinHint.variables.map(_.name).reduceLeft(_ + ", " + _), s"Unable to plan hash join. Instead, constructed\n$plan")
+ } else {
+ hints.foreach { hint =>
+ context.notificationLogger.log(JoinHintUnfulfillableNotification(hint.variables.map(_.name).toIndexedSeq))
+ }
+ }
+ }
+ }
+
+ private def findUnfulfillableIndexHints(query: PlannerQuery, planContext: PlanContext): Seq[UsingIndexHint] = {
+ query.allHints.flatMap {
+ // using index name:label(property1,property2)
+ case UsingIndexHint(_, LabelName(label), properties, _)
+ if planContext.indexExistsForLabelAndProperties(label, properties.map(_.name)) => None
+ // no such index exists
+ case hint: UsingIndexHint => Some(hint)
+ // don't care about other hints
+ case _ => None
+ }
+ }
+
+ private def findUnfulfillableJoinHints(query: PlannerQuery, planContext: PlanContext): Seq[UsingJoinHint] = {
+ query.allHints.collect {
+ case hint: UsingJoinHint => hint
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/externel-properties/scala/org/neo4j/cypher/internal/runtime/interpreted/InterpretedPipeBuilder.scala b/external-properties/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/InterpretedPipeBuilder.scala
similarity index 96%
rename from src/externel-properties/scala/org/neo4j/cypher/internal/runtime/interpreted/InterpretedPipeBuilder.scala
rename to external-properties/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/InterpretedPipeBuilder.scala
index 5fa45b62..8eb81413 100644
--- a/src/externel-properties/scala/org/neo4j/cypher/internal/runtime/interpreted/InterpretedPipeBuilder.scala
+++ b/external-properties/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/InterpretedPipeBuilder.scala
@@ -19,7 +19,7 @@
*/
package org.neo4j.cypher.internal.runtime.interpreted
-import cn.graiph.util.Logging
+import cn.pandadb.externalprops.{ExternalPropertiesContext, CustomPropertyNodeStore}
import org.neo4j.cypher.internal.ir.v3_5.VarPatternLength
import org.neo4j.cypher.internal.planner.v3_5.spi.TokenContext
import org.neo4j.cypher.internal.runtime.ProcedureCallMode
@@ -28,16 +28,16 @@ import org.neo4j.cypher.internal.runtime.interpreted.commands.convert.PatternCon
import org.neo4j.cypher.internal.runtime.interpreted.commands.convert.{ExpressionConverters, InterpretedCommandProjection}
import org.neo4j.cypher.internal.runtime.interpreted.commands.expressions.{AggregationExpression, Literal, ShortestPathExpression}
import org.neo4j.cypher.internal.runtime.interpreted.commands.predicates.{Predicate, True}
-import org.neo4j.cypher.internal.runtime.interpreted.pipes._
+import org.neo4j.cypher.internal.runtime.interpreted.pipes.{FilterPipe, _}
import org.neo4j.cypher.internal.v3_5.ast.semantics.SemanticTable
import org.neo4j.cypher.internal.v3_5.expressions.{Equals => ASTEquals, Expression => ASTExpression, _}
import org.neo4j.cypher.internal.v3_5.logical.plans
import org.neo4j.cypher.internal.v3_5.logical.plans.{ColumnOrder, Limit => LimitPlan, LoadCSV => LoadCSVPlan, Skip => SkipPlan, _}
import org.neo4j.cypher.internal.v3_5.util.attribution.Id
import org.neo4j.cypher.internal.v3_5.util.{Eagerly, InternalException}
-import org.neo4j.kernel.impl.Settings
import org.neo4j.values.AnyValue
import org.neo4j.values.virtual.{NodeValue, RelationshipValue}
+import cn.pandadb.util._
/**
* Responsible for turning a logical plan with argument pipes into a new pipe.
@@ -50,6 +50,8 @@ case class InterpretedPipeBuilder(recurse: LogicalPlan => Pipe,
tokenContext: TokenContext)
(implicit semanticTable: SemanticTable) extends PipeBuilder with Logging {
+ val nodeStore: Option[CustomPropertyNodeStore] = ExternalPropertiesContext.maybeCustomPropertyNodeStore
+
private def getBuildExpression(id: Id) = rewriteAstExpression andThen
((e: ASTExpression) => expressionConverters.toCommandExpression(id, e)) andThen
(expression => expression.rewrite(KeyTokenResolver.resolveExpressions(_, tokenContext)))
@@ -124,29 +126,21 @@ case class InterpretedPipeBuilder(recurse: LogicalPlan => Pipe,
case Selection(predicate, _) =>
val predicateExpression =
if (predicate.exprs.size == 1) buildExpression(predicate.exprs.head) else buildExpression(predicate)
-
- //NOTE: push down predicate
- if (Settings._hookEnabled) {
+ val pipe = FilterPipe(source, predicateExpression)(id = id)
+ //NOTE: predicate push down
+ if (nodeStore.isDefined) {
source match {
case x: AllNodesScanPipe =>
- x.predicatePushDown(predicateExpression);
- case _ => logger.debug("push down predicate: Pipe no match")
+ x.pushDownPredicate(nodeStore.get, pipe, predicateExpression)
+ case x: NodeByLabelScanPipe =>
+ x.pushDownPredicate(nodeStore.get, pipe, predicateExpression, x.label.name)
+ case _ =>
}
}
-
- FilterPipe(source, predicateExpression)(id = id)
+ pipe
case Expand(_, fromName, dir, types: Seq[RelTypeName], toName, relName, ExpandAll) =>
- (Settings._patternMatchFirst, source) match {
- //NOTE: yes! we use pattern match first!!
- case (true, FilterPipe(source2, predicate2)) => {
- logger.debug(s"perform pattern match first!");
- FilterPipe(ExpandAllPipe(source2, fromName, relName, toName, dir, LazyTypes(types.toArray))(id = id), predicate2)(id = source.id)
- }
-
- //default behavier: use where predicate first
- case _ => ExpandAllPipe(source, fromName, relName, toName, dir, LazyTypes(types.toArray))(id = id)
- }
+ ExpandAllPipe(source, fromName, relName, toName, dir, LazyTypes(types.toArray))(id = id)
case Expand(_, fromName, dir, types: Seq[RelTypeName], toName, relName, ExpandInto) =>
ExpandIntoPipe(source, fromName, relName, toName, dir, LazyTypes(types.toArray))(id = id)
diff --git a/external-properties/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/pipes/AllNodesScanPipe.scala b/external-properties/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/pipes/AllNodesScanPipe.scala
new file mode 100644
index 00000000..6f13894f
--- /dev/null
+++ b/external-properties/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/pipes/AllNodesScanPipe.scala
@@ -0,0 +1,23 @@
+package org.neo4j.cypher.internal.runtime.interpreted.pipes
+
+import org.neo4j.cypher.internal.runtime.interpreted._
+import org.neo4j.cypher.internal.v3_5.util.attribution.Id
+import org.neo4j.values.virtual.{NodeValue, VirtualNodeValue}
+
+case class AllNodesScanPipe(ident: String)(val id: Id = Id.INVALID_ID) extends PredicatePushDownPipe {
+
+ protected def internalCreateResults(state: QueryState): Iterator[ExecutionContext] = {
+ val baseContext = state.newExecutionContext(executionContextFactory)
+ var nodes: Option[Iterable[VirtualNodeValue]] = None
+ if (nodeStore.isDefined && predicate.isDefined && fatherPipe != null) {
+ nodes = fetchNodes(state, baseContext)
+ }
+ val nodesIterator = nodes match {
+ case Some(x) =>
+ x.iterator
+ case None =>
+ state.query.nodeOps.all
+ }
+ nodesIterator.map(n => executionContextFactory.copyWith(baseContext, ident, n))
+ }
+}
\ No newline at end of file
diff --git a/external-properties/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/pipes/CreatePipe.scala b/external-properties/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/pipes/CreatePipe.scala
new file mode 100644
index 00000000..fecdfa33
--- /dev/null
+++ b/external-properties/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/pipes/CreatePipe.scala
@@ -0,0 +1,206 @@
+/*
+ * Copyright (c) 2002-2019 "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.cypher.internal.runtime.interpreted.pipes
+
+import org.neo4j.cypher.internal.runtime.interpreted._
+import org.neo4j.cypher.internal.runtime.interpreted.commands.expressions.Expression
+import org.neo4j.cypher.internal.runtime.{LenientCreateRelationship, Operations, QueryContext}
+import org.neo4j.function.ThrowingBiConsumer
+import org.neo4j.values.AnyValue
+import org.neo4j.values.storable.{Value, Values}
+import org.neo4j.values.virtual.{NodeValue, RelationshipValue, VirtualNodeValue}
+import org.neo4j.cypher.internal.v3_5.util.attribution.Id
+import org.neo4j.cypher.internal.v3_5.util.{CypherTypeException, InternalException, InvalidSemanticsException}
+
+/**
+ * Extends PipeWithSource with methods for setting properties and labels on entities.
+ */
+abstract class BaseCreatePipe(src: Pipe) extends PipeWithSource(src) {
+
+ /**
+ * Set properties on node by delegating to `setProperty`.
+ */
+ protected def setProperties(context: ExecutionContext,
+ state: QueryState,
+ entityId: Long,
+ properties: Expression,
+ ops: Operations[_]): Unit =
+ properties(context, state) match {
+ case _: NodeValue | _: RelationshipValue =>
+ throw new CypherTypeException("Parameter provided for node creation is not a Map")
+ case IsMap(map) =>
+ map(state.query).foreach(new ThrowingBiConsumer[String, AnyValue, RuntimeException] {
+ override def accept(k: String, v: AnyValue): Unit = setProperty(entityId, k, v, state.query, ops)
+ })
+
+ case _ =>
+ throw new CypherTypeException("Parameter provided for node creation is not a Map")
+ }
+
+ /**
+ * Set property on node, or call `handleNoValue` if value is `NO_VALUE`.
+ */
+ protected def setProperty(entityId: Long,
+ key: String,
+ value: AnyValue,
+ qtx: QueryContext,
+ ops: Operations[_]): Unit = {
+ //do not set properties for null values
+ if (value == Values.NO_VALUE) {
+ handleNoValue(key)
+ } else {
+ val propertyKeyId = qtx.getOrCreatePropertyKeyId(key)
+ ops.setProperty(entityId, propertyKeyId, makeValueNeoSafe(value))
+ }
+ }
+
+ /**
+ * Callback for when setProperty encounters a NO_VALUE
+ *
+ * @param key the property key associated with the NO_VALUE
+ */
+ protected def handleNoValue(key: String): Unit
+}
+
+/**
+ * Extend BaseCreatePipe with methods to create nodes and relationships from commands.
+ */
+abstract class EntityCreatePipe(src: Pipe) extends BaseCreatePipe(src) {
+
+ /**
+ * Create node from command.
+ */
+ protected def createNode(context: ExecutionContext,
+ state: QueryState,
+ data: CreateNodeCommand): (String, NodeValue) = {
+ val labelIds = data.labels.map(_.getOrCreateId(state.query).id).toArray
+ val node = state.query.createNode(labelIds)
+ data.properties.foreach(setProperties(context, state, node.id(), _, state.query.nodeOps))
+ data.idName -> node
+ }
+
+ /**
+ * Create relationship from command.
+ */
+ protected def createRelationship(context: ExecutionContext,
+ state: QueryState,
+ data: CreateRelationshipCommand): (String, AnyValue) = {
+ val start = getNode(context, data.idName, data.startNode, state.lenientCreateRelationship)
+ val end = getNode(context, data.idName, data.endNode, state.lenientCreateRelationship)
+
+ val relationship =
+ if (start == null || end == null)
+ Values.NO_VALUE // lenient create relationship NOOPs on missing node
+ else {
+ val typeId = data.relType.typ(state.query)
+ val relationship = state.query.createRelationship(start.id(), end.id(), typeId)
+ data.properties.foreach(setProperties(context, state, relationship.id(), _, state.query.relationshipOps))
+ relationship
+ }
+ data.idName -> relationship
+ }
+
+ private def getNode(row: ExecutionContext, relName: String, name: String, lenient: Boolean): VirtualNodeValue =
+ row.get(name) match {
+ case Some(n: VirtualNodeValue) => n
+ case Some(Values.NO_VALUE) =>
+ if (lenient) null
+ else throw new InternalException(LenientCreateRelationship.errorMsg(relName, name))
+ case Some(x) => throw new InternalException(s"Expected to find a node at '$name' but found instead: $x")
+ case None => throw new InternalException(s"Expected to find a node at '$name' but found instead: null")
+ }
+}
+
+/**
+ * Creates nodes and relationships from the constructor commands.
+ */
+case class CreatePipe(src: Pipe, nodes: Array[CreateNodeCommand], relationships: Array[CreateRelationshipCommand])
+ (val id: Id = Id.INVALID_ID) extends EntityCreatePipe(src) {
+
+ override def internalCreateResults(input: Iterator[ExecutionContext], state: QueryState): Iterator[ExecutionContext] =
+ input.map(row => {
+ nodes.foreach { nodeCommand =>
+ val (key, node) = createNode(row, state, nodeCommand)
+ row.set(key, node)
+ }
+
+ relationships.foreach{ relCommand =>
+ val (key, node) = createRelationship(row, state, relCommand)
+ row.set(key, node)
+ }
+
+ row
+ })
+
+ override protected def handleNoValue(key: String) {
+ // do nothing
+ }
+}
+
+case class CreateNodeCommand(idName: String,
+ labels: Seq[LazyLabel],
+ properties: Option[Expression])
+
+case class CreateRelationshipCommand(idName: String,
+ startNode: String,
+ relType: LazyType,
+ endNode: String,
+ properties: Option[Expression])
+
+/**
+ * Create a node corresponding to the constructor command.
+ *
+ * Differs from CreatePipe in that it throws on NO_VALUE properties. Merge cannot use null properties,
+ * * since in that case the match part will not find the result of the create.
+ */
+case class MergeCreateNodePipe(src: Pipe, data: CreateNodeCommand)
+ (val id: Id = Id.INVALID_ID) extends EntityCreatePipe(src) {
+
+ override def internalCreateResults(input: Iterator[ExecutionContext], state: QueryState): Iterator[ExecutionContext] =
+ input.map(inRow => {
+ val (idName, node) = createNode(inRow, state, data)
+ inRow.copyWith(idName, node)
+ })
+
+ override protected def handleNoValue(key: String): Unit = {
+ throw new InvalidSemanticsException(s"Cannot merge node using null property value for $key")
+ }
+}
+
+/**
+ * Create a relationship corresponding to the constructor command.
+ *
+ * Differs from CreatePipe in that it throws on NO_VALUE properties. Merge cannot use null properties,
+ * since in that case the match part will not find the result of the create.
+ */
+case class MergeCreateRelationshipPipe(src: Pipe, data: CreateRelationshipCommand)
+ (val id: Id = Id.INVALID_ID)
+ extends EntityCreatePipe(src) {
+
+ override def internalCreateResults(input: Iterator[ExecutionContext], state: QueryState): Iterator[ExecutionContext] =
+ input.map(inRow => {
+ val (idName, relationship) = createRelationship(inRow, state, data)
+ inRow.copyWith(idName, relationship)
+ })
+
+ override protected def handleNoValue(key: String): Unit = {
+ throw new InvalidSemanticsException(s"Cannot merge relationship using null property value for $key")
+ }
+}
\ No newline at end of file
diff --git a/src/externel-properties/scala/org/neo4j/cypher/internal/runtime/interpreted/pipes/ExpandAllPipe.scala b/external-properties/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/pipes/ExpandAllPipe.scala
similarity index 64%
rename from src/externel-properties/scala/org/neo4j/cypher/internal/runtime/interpreted/pipes/ExpandAllPipe.scala
rename to external-properties/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/pipes/ExpandAllPipe.scala
index 42fc4fbc..7b234e50 100644
--- a/src/externel-properties/scala/org/neo4j/cypher/internal/runtime/interpreted/pipes/ExpandAllPipe.scala
+++ b/external-properties/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/pipes/ExpandAllPipe.scala
@@ -1,3 +1,22 @@
+/*
+ * Copyright (c) 2002-2019 "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.cypher.internal.runtime.interpreted.pipes
import org.neo4j.cypher.internal.runtime.interpreted.ExecutionContext
@@ -6,7 +25,7 @@ import org.neo4j.cypher.internal.v3_5.util.attribution.Id
import org.neo4j.cypher.internal.v3_5.expressions.SemanticDirection
import org.neo4j.values.AnyValue
import org.neo4j.values.storable.Values
-import org.neo4j.values.virtual.{RelationshipValue, NodeValue}
+import org.neo4j.values.virtual.{NodeValue, RelationshipValue, VirtualNodeValue}
case class ExpandAllPipe(source: Pipe,
fromName: String,
@@ -20,7 +39,7 @@ case class ExpandAllPipe(source: Pipe,
input.flatMap {
row =>
getFromNode(row) match {
- case n: NodeValue =>
+ case n: VirtualNodeValue =>
val relationships: Iterator[RelationshipValue] = state.query.getRelationshipsForIds(n.id(), dir, types.types(state.query))
relationships.map { r =>
val other = r.otherNode(n)
@@ -38,4 +57,4 @@ case class ExpandAllPipe(source: Pipe,
def getFromNode(row: ExecutionContext): AnyValue =
row.getOrElse(fromName, throw new InternalException(s"Expected to find a node at '$fromName' but found nothing"))
-}
+}
\ No newline at end of file
diff --git a/external-properties/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/pipes/FilterPipe.scala b/external-properties/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/pipes/FilterPipe.scala
new file mode 100644
index 00000000..bc29797d
--- /dev/null
+++ b/external-properties/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/pipes/FilterPipe.scala
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2002-2019 "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.cypher.internal.runtime.interpreted.pipes
+
+import org.neo4j.cypher.internal.runtime.interpreted.ExecutionContext
+import org.neo4j.cypher.internal.runtime.interpreted.commands.expressions.Expression
+import org.neo4j.values.storable.Values
+import org.neo4j.cypher.internal.v3_5.util.attribution.Id
+
+case class FilterPipe(source: Pipe, predicate: Expression)
+ (val id: Id = Id.INVALID_ID) extends PipeWithSource(source) {
+
+ var _optBypass: Boolean = false
+
+ def bypass(isBypass: Boolean = true): Unit = {
+ _optBypass = isBypass
+ }
+
+ predicate.registerOwningPipe(this)
+
+ protected def internalCreateResults(input: Iterator[ExecutionContext], state: QueryState): Iterator[ExecutionContext] = {
+ if (_optBypass) input else input.filter (ctx => predicate (ctx, state) eq Values.TRUE)
+ }
+}
\ No newline at end of file
diff --git a/src/externel-properties/scala/org/neo4j/cypher/internal/runtime/interpreted/pipes/NodeByLabelScanPipe.scala b/external-properties/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/pipes/NodeByLabelScanPipe.scala
similarity index 57%
rename from src/externel-properties/scala/org/neo4j/cypher/internal/runtime/interpreted/pipes/NodeByLabelScanPipe.scala
rename to external-properties/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/pipes/NodeByLabelScanPipe.scala
index 397ce927..35d84b64 100644
--- a/src/externel-properties/scala/org/neo4j/cypher/internal/runtime/interpreted/pipes/NodeByLabelScanPipe.scala
+++ b/external-properties/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/pipes/NodeByLabelScanPipe.scala
@@ -21,39 +21,27 @@ package org.neo4j.cypher.internal.runtime.interpreted.pipes
import org.neo4j.cypher.internal.runtime.interpreted.ExecutionContext
import org.neo4j.cypher.internal.v3_5.util.attribution.Id
-
-import scala.collection.mutable
-import org.neo4j.kernel.impl.CustomPropertyNodeStoreHolder
-import org.neo4j.values.virtual.NodeValue
+import org.neo4j.values.virtual.{NodeValue, VirtualNodeValue}
case class NodeByLabelScanPipe(ident: String, label: LazyLabel)
- (val id: Id = Id.INVALID_ID) extends Pipe {
+ (val id: Id = Id.INVALID_ID) extends PredicatePushDownPipe {
protected def internalCreateResults(state: QueryState): Iterator[ExecutionContext] = {
- /*
- label.getOptId(state.query) match {
- case Some(labelId) =>
- val nodes = state.query.getNodesByLabel(labelId.id)
- nodes.foreach(n=>println(n.id()))
- val baseContext = state.newExecutionContext(executionContextFactory)
- nodes.map(n => executionContextFactory.copyWith(baseContext, ident, n))
- case None =>
- Iterator.empty
- }
- */
- // todo: optimize
label.getOptId(state.query) match {
case Some(labelId) =>
- //val nodes = state.query.getNodesByLabel(labelId.id)
- val customPropertyNodes = CustomPropertyNodeStoreHolder.get.getNodesByLabel(label.name)
- val nodesArray = mutable.ArrayBuffer[NodeValue]()
- customPropertyNodes.foreach(v=>{
- nodesArray.append(v.toNeo4jNodeValue())
- })
- val nodes = nodesArray.toIterator
val baseContext = state.newExecutionContext(executionContextFactory)
- nodes.map(n => executionContextFactory.copyWith(baseContext, ident, n))
+ var nodes: Option[Iterable[VirtualNodeValue]] = None
+ if (nodeStore.isDefined && fatherPipe != null) {
+ nodes = fetchNodes(state, baseContext)
+ }
+ val nodesIterator = nodes match {
+ case Some(x) =>
+ x.iterator
+ case None =>
+ state.query.getNodesByLabel(labelId.id)
+ }
+ nodesIterator.map(n => executionContextFactory.copyWith(baseContext, ident, n))
case None =>
Iterator.empty
}
diff --git a/external-properties/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/pipes/PredicatePushDownPipe.scala b/external-properties/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/pipes/PredicatePushDownPipe.scala
new file mode 100644
index 00000000..582acacb
--- /dev/null
+++ b/external-properties/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/pipes/PredicatePushDownPipe.scala
@@ -0,0 +1,103 @@
+package org.neo4j.cypher.internal.runtime.interpreted.pipes
+
+import cn.pandadb.externalprops.CustomPropertyNodeStore
+import org.neo4j.cypher.internal.runtime.interpreted.commands.expressions.{Expression, ParameterExpression, Property, SubstringFunction, ToIntegerFunction}
+import org.neo4j.cypher.internal.runtime.interpreted.commands.predicates._
+import org.neo4j.cypher.internal.runtime.interpreted.commands.values.KeyToken
+import org.neo4j.cypher.internal.runtime.interpreted.{NFPredicate, _}
+import org.neo4j.cypher.internal.v3_5.util.{Fby, Last, NonEmptyList}
+import org.neo4j.values.storable.{StringValue, Values}
+import org.neo4j.values.virtual.{NodeValue, VirtualNodeValue, VirtualValues}
+
+trait PredicatePushDownPipe extends Pipe{
+
+ var nodeStore: Option[CustomPropertyNodeStore] = None
+
+ var fatherPipe: Option[FilterPipe] = None
+
+ var predicate: Option[Expression] = None
+
+ var labelName: String = null
+
+ def pushDownPredicate(nodeStore: CustomPropertyNodeStore, fatherPipe: FilterPipe, predicate: Expression, label: String = null): Unit = {
+ this.nodeStore = Some(nodeStore)
+ this.fatherPipe = Some(fatherPipe)
+ this.predicate = Some(predicate)
+ this.labelName = label
+ }
+
+ private def convertPredicate(expression: Expression, state: QueryState, baseContext: ExecutionContext): NFPredicate = {
+ val expr: NFPredicate = expression match {
+ case GreaterThan(a: Property, b: ParameterExpression) =>
+ NFGreaterThan(a.propertyKey.name, b.apply(baseContext, state))
+ case GreaterThanOrEqual(a: Property, b: ParameterExpression) =>
+ NFGreaterThanOrEqual(a.propertyKey.name, b.apply(baseContext, state))
+ case LessThan(a: Property, b: ParameterExpression) =>
+ NFLessThan(a.propertyKey.name, b.apply(baseContext, state))
+ case LessThanOrEqual(a: Property, b: ParameterExpression) =>
+ NFLessThanOrEqual(a.propertyKey.name, b.apply(baseContext, state))
+ case Equals(a: Property, b: ParameterExpression) =>
+ NFEquals(a.propertyKey.name, b.apply(baseContext, state))
+ case Contains(a: Property, b: ParameterExpression) =>
+ NFContainsWith(a.propertyKey.name, b.apply(baseContext, state).asInstanceOf[StringValue].stringValue())
+ case StartsWith(a: Property, b: ParameterExpression) =>
+ NFStartsWith(a.propertyKey.name, b.apply(baseContext, state).asInstanceOf[StringValue].stringValue())
+ case EndsWith(a: Property, b: ParameterExpression) =>
+ NFEndsWith(a.propertyKey.name, b.apply(baseContext, state).asInstanceOf[StringValue].stringValue())
+ case RegularExpression(a: Property, b: ParameterExpression) =>
+ NFRegexp(a.propertyKey.name, b.apply(baseContext, state).asInstanceOf[StringValue].stringValue())
+ case PropertyExists(variable: Expression, propertyKey: KeyToken) =>
+ NFHasProperty(propertyKey.name)
+ case x: Ands =>
+ convertComboPredicatesLoop(NFAnd, x.predicates, state, baseContext)
+ case x: Ors =>
+ convertComboPredicatesLoop(NFOr, x.predicates, state, baseContext)
+ case Not(p) =>
+ val innerP: NFPredicate = convertPredicate(p, state, baseContext)
+ if (innerP == null) null else NFNot(innerP)
+ case _ =>
+ null
+ }
+ expr
+ }
+
+ private def convertComboPredicatesLoop(f: (NFPredicate, NFPredicate) => NFPredicate,
+ expression: NonEmptyList[Predicate],
+ state: QueryState,
+ baseContext: ExecutionContext): NFPredicate = {
+ val lhs = convertPredicate(expression.head, state, baseContext)
+ val rhs = if (expression.tailOption.isDefined) convertComboPredicatesLoop(f, expression.tailOption.get, state, baseContext) else null
+ if (rhs == null) {
+ lhs
+ }
+ else {
+ f(lhs, rhs)
+ }
+ }
+
+ def fetchNodes(state: QueryState, baseContext: ExecutionContext): Option[Iterable[VirtualNodeValue]] = {
+ predicate match {
+ case Some(p) =>
+ val expr: NFPredicate = convertPredicate(p, state, baseContext)
+ if (expr != null && (expr.isInstanceOf[NFAnd] || expr.isInstanceOf[NFOr] || expr.isInstanceOf[NFContainsWith])) {// only enable ppd when NFAnd, NFor
+ fatherPipe.get.bypass()
+ if (labelName != null) {
+ Some(nodeStore.get.getNodeBylabelAndFilter(labelName, expr).map(id => VirtualValues.node(id)))
+ }
+ else {
+ Some(nodeStore.get.filterNodes(expr).map(id => VirtualValues.node(id)))
+ }
+ }
+ else {
+ if (labelName != null) {
+ Some(nodeStore.get.getNodesByLabel(labelName).map(id => VirtualValues.node(id)))
+ }
+ else {
+ None
+ }
+ }
+ case None =>
+ None
+ }
+ }
+}
diff --git a/src/externel-properties/scala/org/neo4j/cypher/internal/runtime/interpreted/predicates.scala b/external-properties/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/predicates.scala
similarity index 95%
rename from src/externel-properties/scala/org/neo4j/cypher/internal/runtime/interpreted/predicates.scala
rename to external-properties/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/predicates.scala
index be158c45..bf492072 100644
--- a/src/externel-properties/scala/org/neo4j/cypher/internal/runtime/interpreted/predicates.scala
+++ b/external-properties/src/main/scala/org/neo4j/cypher/internal/runtime/interpreted/predicates.scala
@@ -63,4 +63,7 @@ case class NFOr(a: NFPredicate, b: NFPredicate) extends NFPredicate {
}
case class NFNot(a: NFPredicate) extends NFPredicate {
+}
+
+case class NFConstantCachedIn(a: NFPredicate) extends NFPredicate {
}
\ No newline at end of file
diff --git a/graiphdb-2019.iml b/graiphdb-2019.iml
deleted file mode 100644
index 67da3a87..00000000
--- a/graiphdb-2019.iml
+++ /dev/null
@@ -1,192 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/itest/cypher-plugins.xml b/itest/cypher-plugins.xml
new file mode 100644
index 00000000..d7f018f3
--- /dev/null
+++ b/itest/cypher-plugins.xml
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/itest/itest/comprehensive/envirTest.properties b/itest/itest/comprehensive/envirTest.properties
new file mode 100644
index 00000000..b15f3f8e
--- /dev/null
+++ b/itest/itest/comprehensive/envirTest.properties
@@ -0,0 +1,2 @@
+zkServerAddr=10.0.86.26:2181
+clusterNodes=10.0.82.216:7685,10.0.82.217:7685,10.0.82.218:7685,10.0.82.219:7685
\ No newline at end of file
diff --git a/itest/itest/performance/1230.txt b/itest/itest/performance/1230.txt
new file mode 100644
index 00000000..f6ebfd23
--- /dev/null
+++ b/itest/itest/performance/1230.txt
@@ -0,0 +1,18 @@
+match (n:person)-[:write_paper]->(p:paper) where p.country = 'United States' return count(n)
+match (n:person)-[:write_paper]->(p:paper) where p.country='United States' and n.citations>800 return count(n)
+match (o:organization)<-[:work_for]-(n:person)-[:write_paper]->(p:paper) where o.country='United States' and n.citations>500 and p.citations>100 return count(n)
+match (n:person)-[:write_paper]->(p:paper) where n.citations>800 and p.citation>100 return p.paperId
+match (o:organization)<-[:work_for] -(n:person)-[:write_paper]->(p:paper) where n.citations>1000 and p.citation<100 and o.latitude>30 return count(p)
+match (o:organization)<-[:work_for] -(n:person)-[:write_paper]->(p:paper)-[:paper_reference]->(p2:paper) where n.citations>1000 and p2.citation<100 and o.country='United States' return count(p)
+match (o:organization)<-[:work_for]-(n:person)-[:write_paper]->(p:paper)-[:paper_reference]->(p2:paper) where n.citations>1000 and p2.citation<100 and o.latitude>30 return count(p)
+match (o:organization)<-[:work_for]-(n:person)-[:write_paper]->(p:paper)-[:paper_belong_topic]->(t:topic) where t.rank<5 and p.citation<100 and o.latitude>30 return count(p)
+match (o:organization)<-[:work_for]-(n:person)-[:write_paper]->(p:paper)-[:paper_belong_topic]->(t:topic) where t.rank<4 and p.citation>100 and o.longitude<130 return p.paperId
+match (o:organization)<-[:work_for]-(n:person)-[:write_paper]->(p:paper)-[:paper_reference]->(p2:paper) where p.citation<100 and p2.citation>100 and o.latitude>30 return count(p)
+match (o:organization)<-[:work_for]-(n:person)-[:write_paper]->(p:paper)-[:paper_reference]->(p2:paper) where p.citation<100 and p2.citation>100 and o.country='United States' return count(p2)
+match (pu:publications)<-[]-(n:person)-[:write_paper]->(p:paper)-[:paper_reference]->(p2:paper) where p.citation<500 and p2.citation>200 and n.nationality='United States' return count(p2)
+match (n:person)-[:write_paper]->(p:paper)-[:be_cited]->(p2:paper) where p.country = 'United States' and p2.citation>500 return n.nameEn
+match (n:person)-[:write_paper]->(p:paper)-[:be_cited]->(p2:paper) where n.citations>1000 and p.citation>100 and p2.citation>500 return p2.paperId
+match (n:person)-[:write_paper]->(p:paper)-[:paper_belong_topic]->(t:topic) where n.citations>1000 and p.citation>100 and t.rank<3 return distinct(n.nameEn)
+match (o:organization)<-[:work_for]-(n:person)-[:write_paper]->(p:paper)-[:paper_belong_topic]->(t:topic) where t.rank<5 and p.citation<100 and o.country='France' return distinct(p.paperId)
+match (o:organization)<-[:work_for]-(n:person)-[:write_paper]->(p:paper)-[:paper_belong_topic]->(t:topic) where t.rank<5 and p.country='United States' and o.citations>10000000 return distinct(n.nameEn)
+match (o:organization)<-[:work_for]-(n:person)-[:write_paper]->(p:paper)-[:be_cited]->(p2:paper) where p2.citation>400 and p.country='United States' and o.citations>10000000 return distinct(n.nameEn)
\ No newline at end of file
diff --git a/itest/itest/performance/cypher.txt b/itest/itest/performance/cypher.txt
new file mode 100644
index 00000000..011e15c8
--- /dev/null
+++ b/itest/itest/performance/cypher.txt
@@ -0,0 +1,30 @@
+Match(n) Return Distinct length(labels(n)), count(length(labels(n)));
+Match(n) Return Distinct labels(n), count(labels(n));
+MATCH (p:person)-[wp:write_paper]->(pa:paper) -[r:paper_belong_topic]-> (t:topic) WHERE toInteger(subString(pa.publishDate,0,4)) >= 1995 AND toInteger(subString(pa.publishDate,0,4)) <= 2019 AND t.topicId IN ['185592680'] AND t.rank = 1 WITH DISTINCT(pa),pa.citation AS citationOfWorld ORDER BY citationOfWorld DESC LIMIT 10000 MATCH (p:person)-[wp:write_paper]->(pa) WHERE p.nationality = 'China' RETURN DISTINCT(toInteger(subString(pa.publishDate,0,4))) AS year, COUNT(pa) AS statistics, '中国' as region Order By year;
+Match(n:person) return n.publications5 Order By n.publication5 DESC;
+MATCH p=(a)-[r1:be_cited]->(b)-[r2:be_cited]->(c) RETURN count(p)
+MATCH p=(a)<-[r1:write_paper]-(b)-[r2:work_for]->(c) RETURN count(p);
+MATCH p=(a)<-[r1:be_cited]-(b)-[r2:be_cited]->(c) RETURN count(p)
+Match(n:paper) where n.language='en' and n.type='Journal' return count(n);
+Match(n:person) Return n.personId Order By n.citations Desc;
+Match(n:person) where n.citations>5 return distinct n.nationality, count(n) Order by count(n) desc;
+Match(n:paper) where toInteger(n.publishDate)>20000000 return distinct n.country, count(n) order by count(n) Desc;
+MATCH (p:person)-[wp:write_paper]->(pa:paper) -[r:paper_belong_topic]-> (t:topic) WHERE toInteger(subString(pa.publishDate,0,4)) >= 1995 AND toInteger(subString(pa.publishDate,0,4)) <= 2019 AND t.topicId IN ['185592680'] AND t.rank = 1 WITH DISTINCT(pa),pa.citation AS citationOfWorld ORDER BY citationOfWorld DESC LIMIT 10000 MATCH (p:person)-[wp:write_paper]->(pa) WHERE p.nationality = 'China' RETURN DISTINCT(toInteger(subString(pa.publishDate,0,4))) AS year, COUNT(pa) AS statistics, '中国' as region order by year UNION ALL MATCH (p:person)-[wp:write_paper]->(pa:paper) -[r:paper_belong_topic]-> (t:topic) WHERE toInteger(subString(pa.publishDate,0,4)) >= 1995 AND toInteger(subString(pa.publishDate,0,4)) <= 2019 AND (t.topicId IN ['185592680']) AND t.rank = 1 WITH DISTINCT(pa) AS pa,pa.citation AS citation ORDER BY citation DESC LIMIT 10000 RETURN DISTINCT(toInteger(subString(pa.publishDate,0,4))) AS year, COUNT(pa) AS statistics, '世界' as region order by year;
+match (n:person)-[:write_paper]->(p:paper) where p.country = 'United States' return count(n)
+match (n:person)-[:write_paper]->(p:paper) where p.country='United States' and n.citations>800 return count(n)
+match (o:organization)<-[:work_for]-(n:person)-[:write_paper]->(p:paper) where o.country='United States' and n.citations>500 and p.citations>100 return count(n)
+match (n:person)-[:write_paper]->(p:paper) where n.citations>800 and p.citation>100 return p.paperId
+match (o:organization)<-[:work_for] -(n:person)-[:write_paper]->(p:paper) where n.citations>1000 and p.citation<100 and o.latitude>30 return count(p)
+match (o:organization)<-[:work_for] -(n:person)-[:write_paper]->(p:paper)-[:paper_reference]->(p2:paper) where n.citations>1000 and p2.citation<100 and o.country='United States' return count(p)
+match (o:organization)<-[:work_for]-(n:person)-[:write_paper]->(p:paper)-[:paper_reference]->(p2:paper) where n.citations>1000 and p2.citation<100 and o.latitude>30 return count(p)
+match (o:organization)<-[:work_for]-(n:person)-[:write_paper]->(p:paper)-[:paper_belong_topic]->(t:topic) where t.rank<5 and p.citation<100 and o.latitude>30 return count(p)
+match (o:organization)<-[:work_for]-(n:person)-[:write_paper]->(p:paper)-[:paper_belong_topic]->(t:topic) where t.rank<4 and p.citation>100 and o.longitude<130 return p.paperId
+match (o:organization)<-[:work_for]-(n:person)-[:write_paper]->(p:paper)-[:paper_reference]->(p2:paper) where p.citation<100 and p2.citation>100 and o.latitude>30 return count(p)
+match (o:organization)<-[:work_for]-(n:person)-[:write_paper]->(p:paper)-[:paper_reference]->(p2:paper) where p.citation<100 and p2.citation>100 and o.country='United States' return count(p2)
+match (pu:publications)<-[]-(n:person)-[:write_paper]->(p:paper)-[:paper_reference]->(p2:paper) where p.citation<500 and p2.citation>200 and n.nationality='United States' return count(p2)
+match (n:person)-[:write_paper]->(p:paper)-[:be_cited]->(p2:paper) where p.country = 'United States' and p2.citation>500 return n.nameEn
+match (n:person)-[:write_paper]->(p:paper)-[:be_cited]->(p2:paper) where n.citations>1000 and p.citation>100 and p2.citation>500 return p2.paperId
+match (n:person)-[:write_paper]->(p:paper)-[:paper_belong_topic]->(t:topic) where n.citations>1000 and p.citation>100 and t.rank<3 return distinct(n.nameEn)
+match (o:organization)<-[:work_for]-(n:person)-[:write_paper]->(p:paper)-[:paper_belong_topic]->(t:topic) where t.rank<5 and p.citation<100 and o.country='France' return distinct(p.paperId)
+match (o:organization)<-[:work_for]-(n:person)-[:write_paper]->(p:paper)-[:paper_belong_topic]->(t:topic) where t.rank<5 and p.country='United States' and o.citations>10000000 return distinct(n.nameEn)
+match (o:organization)<-[:work_for]-(n:person)-[:write_paper]->(p:paper)-[:be_cited]->(p2:paper) where p2.citation>400 and p.country='United States' and o.citations>10000000 return distinct(n.nameEn)
\ No newline at end of file
diff --git a/itest/itest/performance/cypherOnLarge b/itest/itest/performance/cypherOnLarge
new file mode 100644
index 00000000..09f712ce
--- /dev/null
+++ b/itest/itest/performance/cypherOnLarge
@@ -0,0 +1,10 @@
+Match (n:paper) where toInteger(n.publishDate)>20000000 return distinct n.paperType, count(n.paperType);
+Match(n) return distinct labels(n), count(n);
+Match (n)-[r:org_paper]->(m) where n.cnName contains '医院' AND m.citation>5 Return count(m);
+Match(n:person) return n.chineseName Order By n.influenceScore Desc limit 25;
+Match(n:person) With distinct n.chineseName as name, count(n.chineseName) as counts where counts>1 return name, counts;
+Match p=(n1:dictionary_ccs)<-[r1:criterion_belong_ccs]-(n2:criterion)<-[r2:org_criterion]-(n:organization)-[r:org_criterion]->(n3:criterion)-[r3:criterion_belong_ccs]->(n4:dictionary_ccs) Where not n1.dictionaryId = n4.dictionaryId return count(n)
+MAtch(n:paper) where n.paperType contains '期刊' return count(n);
+Match(n:patent) where toInteger(n.awardDate) > 20180000 return count(n);
+Match(n:paper_keywords) where n.times_all>1 return count(n);
+Match(n:patent) where n.chineseName contains '装置' return count(n);
\ No newline at end of file
diff --git a/itest/itest/performance/cypher_0913_graph500 b/itest/itest/performance/cypher_0913_graph500
new file mode 100644
index 00000000..40d7010e
--- /dev/null
+++ b/itest/itest/performance/cypher_0913_graph500
@@ -0,0 +1,5 @@
+Match (n) where id(n)<10 with n Match p=(n)-->(m1)-->(m2) return count(p)
+Match p=(m1)-->(n)<--(m2) where id(n)<10 return count(p)
+Match p=(m1)-->(n)<--(m2) where id(n)<10 and id(m1)>id(m2) return count(p)
+Match (n)-->(m) return distinct n.id, count(m) order by count(m) DESc limit 100
+Match (n)-->(m) return distinct m.id, count(n) order by count(n) DESc limit 100
diff --git a/itest/itest/performance/performanceConf.properties b/itest/itest/performance/performanceConf.properties
new file mode 100644
index 00000000..e513b701
--- /dev/null
+++ b/itest/itest/performance/performanceConf.properties
@@ -0,0 +1,5 @@
+neo4jResultFile=neo4j.txt
+PandaDBResultFile=pandadb.txt
+statementsFile=cypherOnLarge
+boltURI=bolt://10.0.82.220:7687
+zkServerAddr=10.0.82.216:2181,10.0.82.217:2181
\ No newline at end of file
diff --git a/itest/pom.xml b/itest/pom.xml
new file mode 100644
index 00000000..b832ae6b
--- /dev/null
+++ b/itest/pom.xml
@@ -0,0 +1,56 @@
+
+
+
+ parent
+ cn.pandadb
+ 0.0.2
+ ../
+
+ 4.0.0
+
+ cn.pandadb
+ itest
+
+
+
+ org.scala-lang.modules
+ scala-parser-combinators_2.11
+
+
+ cn.pandadb
+ blob-feature
+ ${pandadb.version}
+ compile
+
+
+ cn.pandadb
+ server
+ ${pandadb.version}
+ compile
+
+
+ cn.pandadb
+ tools
+ ${pandadb.version}
+ compile
+
+
+ cn.pandadb
+ aipm-library
+ ${pandadb.version}
+ runtime
+
+
+ org.scalatest
+ scalatest_${scala.compat.version}
+ test
+
+
+ junit
+ junit
+
+
+
+
\ No newline at end of file
diff --git a/itest/src/main/scala/cn/pandadb/itest/performance/PerformanceTests.scala b/itest/src/main/scala/cn/pandadb/itest/performance/PerformanceTests.scala
new file mode 100644
index 00000000..829960dc
--- /dev/null
+++ b/itest/src/main/scala/cn/pandadb/itest/performance/PerformanceTests.scala
@@ -0,0 +1,158 @@
+package perfomance
+
+import java.io.{File, FileInputStream, FileWriter}
+import java.text.SimpleDateFormat
+import java.util.{Date, Properties}
+
+import cn.pandadb.externalprops.{ExternalPropertiesContext, InElasticSearchPropertyNodeStore}
+import cn.pandadb.util.GlobalContext
+import org.neo4j.graphdb.GraphDatabaseService
+import org.neo4j.graphdb.factory.GraphDatabaseFactory
+
+import scala.io.Source
+
+
+trait TestBase {
+
+ def nowDate: String = {
+ val now = new Date
+ val dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
+ dateFormat.format(now)
+ }
+
+}
+
+
+object PerformanceTests extends TestBase {
+
+ var esNodeStores: Option[InElasticSearchPropertyNodeStore] = None
+
+ def createPandaDB(props: Properties): GraphDatabaseService = {
+ var graphPath = ""
+ if (props.containsKey("graph.data.path")) graphPath = props.get("graph.data.path").toString
+ else throw new Exception("Configure File Error: graph.data.path is not exist! ")
+ val graphFile = new File(graphPath)
+ if (!graphFile.exists) throw new Exception(String.format("Error: GraphPath(%s) is not exist! ", graphPath))
+
+ val esHost = props.getProperty("external.properties.store.es.host")
+ val esPort = props.getProperty("external.properties.store.es.port").toInt
+ val esSchema = props.getProperty("external.properties.store.es.schema")
+ val esIndex = props.getProperty("external.properties.store.es.index")
+ val esType = props.getProperty("external.properties.store.es.type")
+ val esScrollSize = props.getProperty("external.properties.store.es.scroll.size", "1000").toInt
+ val esScrollTime = props.getProperty("external.properties.store.es.scroll.time.minutes", "10").toInt
+ val esNodeStore = new InElasticSearchPropertyNodeStore(esHost, esPort, esIndex, esType, esSchema, esScrollSize, esScrollTime)
+ ExternalPropertiesContext.bindCustomPropertyNodeStore(esNodeStore)
+ GlobalContext.setLeaderNode(true)
+ esNodeStores = Some(esNodeStore)
+
+ new GraphDatabaseFactory().newEmbeddedDatabase(graphFile)
+ }
+
+ def createNeo4jDB(props: Properties): GraphDatabaseService = {
+ var graphPath = ""
+ if (props.containsKey("graph.data.path")) graphPath = props.get("graph.data.path").toString
+ else throw new Exception("Configure File Error: graph.data.path is not exist! ")
+ val graphFile = new File(graphPath)
+ if (!graphFile.exists) throw new Exception(String.format("Error: GraphPath(%s) is not exist! ", graphPath))
+
+ new GraphDatabaseFactory().newEmbeddedDatabase(graphFile)
+ }
+
+ def main(args: Array[String]): Unit = {
+
+ var propFilePath = "/home/bigdata/pandadb-2019/itest/testdata/performance-test.conf" // null;
+ if (args.length > 0) propFilePath = args(0)
+ val props = new Properties
+ props.load(new FileInputStream(new File(propFilePath)))
+
+ var testDb = "neo4j"
+ var graphPath = ""
+ var logFileDir = ""
+ var cyhperFilePath = ""
+
+ if (props.containsKey("test.db")) testDb = props.get("test.db").toString.toLowerCase()
+ else throw new Exception("Configure File Error: test.db is not exist! ")
+
+ if (props.containsKey("graph.data.path")) graphPath = props.get("graph.data.path").toString
+ else throw new Exception("Configure File Error: graph.data.path is not exist! ")
+
+ if (props.containsKey("log.file.dir")) logFileDir = props.get("log.file.dir").toString
+ else throw new Exception("Configure File Error: log.file.dir is not exist! ")
+
+ if (props.containsKey("test.cyhper.path")) cyhperFilePath = props.get("test.cyhper.path").toString
+ else throw new Exception("Configure File Error: test.cyhper.path is not exist! ")
+
+ val logDir: File = new File(logFileDir)
+ if (!logDir.exists()) {
+ logDir.mkdirs
+ println("make log dir")
+ }
+ val logFileName = new SimpleDateFormat("MMdd-HHmmss").format(new Date) + ".log"
+ val logFile = new File(logDir, logFileName)
+
+ println("Neo4j Test")
+ println(s"GraphDataPath: ${graphPath} \n LogFilePath: ${logFile.getAbsolutePath}")
+
+ val logFw = new FileWriter(logFile)
+ logFw.write(s"GraphDataPath: $graphPath \n")
+ val cyhpers = readCyphers(cyhperFilePath)
+ var db: GraphDatabaseService = null
+
+ if (testDb.equals("neo4j")) {
+ println(s"testDB: neo4j \n")
+ logFw.write(s"testDB: neo4j \n")
+ db = createNeo4jDB(props)
+ }
+ else if (testDb.equals("pandadb")) {
+ println(s"testDB: pandadb \n")
+ logFw.write(s"testDB: pandadb \n")
+ db = createPandaDB(props)
+ }
+
+ if (db == null) {
+ throw new Exception("DB is null")
+ }
+
+ println("==== begin tests ====")
+ val beginTime = nowDate
+ println(beginTime)
+
+ try {
+ var i = 0
+ cyhpers.foreach(cyhper => {
+ i += 1
+ val tx = db.beginTx()
+ val mills0 = System.currentTimeMillis()
+ val res = db.execute(cyhper)
+ val useMills = System.currentTimeMillis() - mills0
+ tx.close()
+ println(s"$i, $useMills")
+ logFw.write(s"\n====\n$cyhper\n")
+ logFw.write(s"UsedTime(ms): $useMills \n")
+ logFw.flush()
+ })
+ }
+ finally {
+ logFw.close()
+ if (testDb == "pandadb" && esNodeStores.isDefined) {
+ esNodeStores.get.esClient.close()
+ }
+ db.shutdown()
+ }
+
+ println("==== end tests ====")
+ val endTime = nowDate
+ println("Begin Time: " + beginTime)
+ println("End Time: " + endTime)
+
+ }
+
+ def readCyphers(filePath: String): Iterable[String] = {
+ val source = Source.fromFile(filePath, "UTF-8")
+ val lines = source.getLines().toArray
+ source.close()
+ lines
+ }
+
+}
diff --git a/itest/src/test/resources/clusterLog.json b/itest/src/test/resources/clusterLog.json
new file mode 100644
index 00000000..355bd5ca
--- /dev/null
+++ b/itest/src/test/resources/clusterLog.json
@@ -0,0 +1 @@
+{"dataLog":[{"versionNum":1,"command":"Create(n:Test{version:1})"},{"versionNum":2,"command":"Create(n:Test{version:2})"},{"versionNum":3,"command":"Create(n:Test{version:3})"}]}
\ No newline at end of file
diff --git a/itest/src/test/resources/localLog.json b/itest/src/test/resources/localLog.json
new file mode 100644
index 00000000..b9beaeaa
--- /dev/null
+++ b/itest/src/test/resources/localLog.json
@@ -0,0 +1 @@
+{"dataLog":[{"versionNum":1,"command":"Create(n:Test{version:1})"},{"versionNum":2,"command":"Create(n:Test{version:2})"}]}
\ No newline at end of file
diff --git a/src/test/resources/log4j.properties b/itest/src/test/resources/log4j.properties
similarity index 94%
rename from src/test/resources/log4j.properties
rename to itest/src/test/resources/log4j.properties
index 7fd556b6..f9f8ea82 100644
--- a/src/test/resources/log4j.properties
+++ b/itest/src/test/resources/log4j.properties
@@ -5,7 +5,7 @@ log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=[%d{HH:mm:ss:SSS}] %-5p %-20c{1} :: %m%n
#log4j.logger.org.neo4j.values.storable=DEBUG
-log4j.logger.org.neo4j=WARN
+log4j.logger.org.neo4j=DEBUG
log4j.logger.org.neo4j.server=DEBUG
log4j.logger.cn=DEBUG
log4j.logger.org=WARN
diff --git a/itest/src/test/scala/PandaDBTestBase.scala b/itest/src/test/scala/PandaDBTestBase.scala
new file mode 100644
index 00000000..c94b3b34
--- /dev/null
+++ b/itest/src/test/scala/PandaDBTestBase.scala
@@ -0,0 +1,74 @@
+import java.io.{File, FileInputStream}
+import java.util.Properties
+import java.util.concurrent.{ExecutorService, Executors}
+
+import cn.pandadb.network.NodeAddress
+import cn.pandadb.tool.PNodeServerStarter
+import org.junit.Test
+import org.neo4j.driver.{Driver, GraphDatabase, StatementResult}
+
+/**
+ * @Author: Airzihao
+ * @Description:
+ * @Date: Created at 12:04 2019/12/26
+ * @Modified By:
+ */
+abstract class PandaDBTestBase {
+ var serialNum = 0
+ val threadPool: ExecutorService = Executors.newFixedThreadPool(3)
+
+// def startLocalPNodeServer(): NodeAddress = {
+// val startCmd = s"cmd.exe /c mvn exec:java -Dexec.mainClass='cn.pandadb.tool.UnsafePNodeLauncher' -Dexec.args=${serialNum}"
+// startCmd !!;
+// val localNodeAddress = _getLocalNodeAddressFromFile(new File(s"./itest/testdata/localnode${serialNum}.conf"))
+// serialNum += 1;
+// Thread.sleep(10000)
+// localNodeAddress
+// }
+
+ def standAloneLocalPNodeServer(): NodeAddress = {
+ threadPool.execute(new UnsafePNodeThread(serialNum))
+ print(22222)
+ Thread.sleep(10000)
+ val localNodeAddress = _getLocalNodeAddressFromFile(new File(s"./testdata/localnode${serialNum}.conf"))
+ serialNum += 1
+ localNodeAddress
+ }
+
+ def executeCypher(driver: Driver, cypher: String): StatementResult = {
+ val session = driver.session()
+ val tx = session.beginTransaction()
+ val result = tx.run(cypher)
+ tx.success()
+ tx.close()
+ session.close()
+ result
+ }
+ // For base test's use.
+ private def _getLocalNodeAddressFromFile(confFile: File): NodeAddress = {
+ val props = new Properties()
+ props.load(new FileInputStream(confFile))
+ NodeAddress.fromString(props.getProperty("node.server.address"))
+ }
+
+}
+
+object ExampleText extends PandaDBTestBase {
+ val driver = GraphDatabase.driver(s"bolt://${standAloneLocalPNodeServer().getAsString}")
+}
+class ExampleText extends PandaDBTestBase {
+ @Test
+ def test1(): Unit = {
+ val localNodeAddress = standAloneLocalPNodeServer()
+ val cypher = ""
+ val result = executeCypher(ExampleText.driver, cypher)
+ }
+}
+
+class UnsafePNodeThread(num: Int) extends Runnable{
+ override def run(): Unit = {
+ //scalastyle:off
+ PNodeServerStarter.main(Array(s"./output/testdb/db${num}",
+ s"./testdata/localnode${num}.conf"));
+ }
+}
\ No newline at end of file
diff --git a/itest/src/test/scala/cypher-plus/BlobValueTest.scala b/itest/src/test/scala/cypher-plus/BlobValueTest.scala
new file mode 100644
index 00000000..40b49b04
--- /dev/null
+++ b/itest/src/test/scala/cypher-plus/BlobValueTest.scala
@@ -0,0 +1,218 @@
+///*
+// * Copyright (c) 2002-2019 "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 .
+// */
+//
+//import java.io.{File, FileInputStream}
+//import java.net.URL
+//
+//import cn.pandadb.blob.Blob
+//import cn.pandadb.driver.RemotePanda
+//import cn.pandadb.server.PNodeServer
+//import org.apache.commons.io.IOUtils
+//import org.neo4j.driver._
+//import org.scalatest.{BeforeAndAfter, FunSuite}
+//
+//class BlobValueTest extends FunSuite with BeforeAndAfter with TestBase {
+// var server: PNodeServer = _;
+//
+// before {
+// setupNewDatabase(new File("./output/testdb/data/databases/graph.db"));
+// server = PNodeServer.startServer(testDbDir, new File(testConfPath));
+// }
+//
+// after {
+// server.shutdown()
+// }
+//
+// test("test blob R/W via cypher") {
+// val conn = RemotePanda.connect("bolt://localhost:7687");
+// //a non-blob
+// val (node, name, age) = conn.querySingleObject("match (n) where n.name='bob' return n, n.name, n.age", (result: Record) => {
+// (result.get("n").asNode(), result.get("n.name").asString(), result.get("n.age").asInt())
+// });
+//
+// assert("bob" === name);
+// assert(40 == age);
+//
+// val nodes = conn.queryObjects("match (n) return n", (result: Record) => {
+// result.get("n").asNode()
+// });
+//
+// assert(2 == nodes.length);
+//
+// //blob
+// val blob0 = conn.querySingleObject("return Blob.empty()", (result: Record) => {
+// result.get(0).asBlob
+// });
+//
+// assert(0 == blob0.length);
+//
+// conn.querySingleObject("return Blob.fromFile('./testdata/test.png')", (result: Record) => {
+// val blob1 = result.get(0).asBlob
+// assert(new File("./testdata/test.png").length() == blob1.toBytes().length);
+// blob1.offerStream(is => {
+// //remote input stream should be closed
+// is.read();
+// })
+// assert(IOUtils.toByteArray(new FileInputStream(new File("./testdata/test.png"))) ===
+// blob1.toBytes());
+// 1;
+// });
+//
+// var blob20: Blob = null;
+//
+// conn.querySingleObject("match (n) where n.name='bob' return n.photo,n.album,Blob.len(n.photo) as len", (result: Record) => {
+// val blob2 = result.get("n.photo").asBlob;
+// blob20 = blob2;
+// val album = result.get("n.album").asList();
+// val len = result.get("len").asInt()
+//
+// assert(len == new File("./testdata/test.png").length());
+//
+// assert(IOUtils.toByteArray(new FileInputStream(new File("./testdata/test.png"))) ===
+// blob2.offerStream {
+// IOUtils.toByteArray(_)
+// });
+//
+// assert(6 == album.size());
+//
+// assert(IOUtils.toByteArray(new FileInputStream(new File("./testdata/test.png"))) ===
+// album.get(0).asInstanceOf[Blob].offerStream {
+// IOUtils.toByteArray(_)
+// });
+// assert(IOUtils.toByteArray(new FileInputStream(new File("./testdata/test.png"))) ===
+// album.get(5).asInstanceOf[Blob].offerStream {
+// IOUtils.toByteArray(_)
+// });
+// });
+//
+// //now, blob is unaccessible
+// val ex =
+// try {
+// blob20.offerStream {
+// IOUtils.toByteArray(_)
+// };
+//
+// false;
+// }
+// catch {
+// case _ => true;
+// }
+//
+// assert(ex);
+//
+// conn.querySingleObject("match (n) where n.name='alex' return n.photo", (result: Record) => {
+// val blob3 = result.get("n.photo").asBlob
+// assert(IOUtils.toByteArray(new FileInputStream(new File("./testdata/test1.png"))) ===
+// blob3.offerStream {
+// IOUtils.toByteArray(_)
+// });
+// });
+//
+// //query with parameters
+// val blob4 = conn.querySingleObject("match (n) where n.name={NAME} return n.photo",
+// Map("NAME" -> "bob"), (result: Record) => {
+// result.get("n.photo").asBlob
+// });
+//
+// //commit new records
+// conn.executeUpdate("CREATE (n {name:{NAME}})",
+// Map("NAME" -> "张三"));
+//
+// conn.executeUpdate("CREATE (n {name:{NAME}, photo:{BLOB_OBJECT}})",
+// Map("NAME" -> "张三", "BLOB_OBJECT" -> Blob.EMPTY));
+//
+// conn.executeUpdate("CREATE (n {name:{NAME}, photo:{BLOB_OBJECT}})",
+// Map("NAME" -> "张三", "BLOB_OBJECT" -> Blob.fromFile(new File("./testdata/test1.png"))));
+//
+// conn.executeQuery("return {BLOB_OBJECT}",
+// Map("BLOB_OBJECT" -> Blob.fromFile(new File("./testdata/test.png"))));
+//
+// conn.querySingleObject("return {BLOB_OBJECT}",
+// Map("BLOB_OBJECT" -> Blob.fromFile(new File("./testdata/test.png"))), (result: Record) => {
+// val blob = result.get(0).asBlob
+//
+// assert(IOUtils.toByteArray(new FileInputStream(new File("./testdata/test.png"))) ===
+// blob.offerStream {
+// IOUtils.toByteArray(_)
+// });
+//
+// });
+// }
+//
+// test("test blob R/W via blob literal") {
+// val conn = RemotePanda.connect("bolt://localhost:7687");
+//
+// //blob
+// val blob0 = conn.querySingleObject("return ", (result: Record) => {
+// result.get(0).asBlob
+// });
+//
+// assert(0 == blob0.length);
+//
+// //blob
+// val blob01 = conn.querySingleObject("return ", (result: Record) => {
+// result.get(0).asBlob
+// });
+//
+// assert("this is an example".getBytes() === blob01.toBytes());
+//
+// //test localfile
+// conn.querySingleObject("return ", (result: Record) => {
+// val blob1 = result.get(0).asBlob
+//
+// assert(IOUtils.toByteArray(new FileInputStream(new File("./testdata/test.png"))) ===
+// blob1.toBytes());
+// });
+//
+// //test http
+// conn.querySingleObject("return ", (result: Record) => {
+// val blob2 = result.get(0).asBlob
+// assert(IOUtils.toByteArray(new URL("https://www.baidu.com/img/baidu_jgylogo3.gif")) ===
+// blob2.toBytes());
+// });
+//
+// //test large files
+// conn.querySingleObject("return ", (result: Record) => {
+// val blob2 = result.get(0).asBlob
+// assert(blob2.length > 10240)
+// val bs = blob2.toBytes()
+// assert(blob2.length === bs.length)
+//
+// val bs2 = IOUtils.toByteArray(new URL("http://img.mp.itc.cn/upload/20160512/5a4ccef302664806bb679a29c82209c5.jpg"))
+// println(bs2.toList)
+// println(bs.toList)
+//
+// assert(bs2.length === bs.length)
+// assert(bs2 === bs);
+// });
+//
+// //test https
+// val blob3 = conn.querySingleObject("return ", (result: Record) => {
+// result.get(0).asBlob.toBytes()
+// });
+//
+// assert(IOUtils.toByteArray(new URL("https://avatars0.githubusercontent.com/u/2328905?s=460&v=4")) ===
+// blob3);
+//
+// assert(conn.querySingleObject("return Blob.len()", (result: Record) => {
+// result.get(0).asInt
+// }) == new File("./testdata/test.png").length());
+// }
+//}
diff --git a/itest/src/test/scala/cypher-plus/SemOpTest.scala b/itest/src/test/scala/cypher-plus/SemOpTest.scala
new file mode 100644
index 00000000..70e818b4
--- /dev/null
+++ b/itest/src/test/scala/cypher-plus/SemOpTest.scala
@@ -0,0 +1,114 @@
+//import java.io.File
+//
+//import cn.pandadb.server.PNodeServer
+//import org.apache.commons.io.FileUtils
+//import org.junit.{Assert, Test}
+//
+//class SemOpTest extends TestBase {
+// @Test
+// def testLike(): Unit = {
+// //create a new database
+// val db = openDatabase();
+// val tx = db.beginTx();
+//
+// Assert.assertEquals(true, db.execute("return Blob.empty() ~:0.5 Blob.empty() as r").next().get("r").asInstanceOf[Boolean]);
+// Assert.assertEquals(true, db.execute("return Blob.empty() ~:0.5 Blob.empty() as r").next().get("r").asInstanceOf[Boolean]);
+// Assert.assertEquals(true, db.execute("return Blob.empty() ~:1.0 Blob.empty() as r").next().get("r").asInstanceOf[Boolean]);
+//
+// Assert.assertEquals(true, db.execute("return Blob.empty() ~: Blob.empty() as r").next().get("r").asInstanceOf[Boolean]);
+//
+// Assert.assertEquals(true, db.execute(
+// """return Blob.fromFile('./testdata/mayun1.jpeg')
+// ~: Blob.fromFile('./testdata/mayun2.jpeg') as r""")
+// .next().get("r").asInstanceOf[Boolean]);
+//
+// Assert.assertEquals(false, db.execute(
+// """return Blob.fromFile('./testdata/mayun1.jpeg')
+// ~: Blob.fromFile('./testdata/lqd.jpeg') as r""")
+// .next().get("r").asInstanceOf[Boolean]);
+//
+// Assert.assertEquals(true, db.execute("""return Blob.fromFile('./testdata/car1.jpg') ~: '.*NB666.*' as r""")
+// .next().get("r").asInstanceOf[Boolean]);
+//
+// tx.success();
+// tx.close();
+// db.shutdown();
+// }
+//
+// @Test
+// def testCompare(): Unit = {
+// //create a new database
+// val db = openDatabase();
+// val tx = db.beginTx();
+//
+// try {
+// Assert.assertEquals(1.toLong, db.execute("return 1 :: 2 as r").next().get("r"));
+// Assert.assertTrue(false);
+// }
+// catch {
+// case _: Throwable => Assert.assertTrue(true);
+// }
+//
+// Assert.assertEquals(true,
+// db.execute("return :: as r").next().get("r").asInstanceOf[Double] > 0.7);
+// Assert.assertEquals(true,
+// db.execute("return :: as r").next().get("r").asInstanceOf[Double] > 0.6);
+// Assert.assertEquals(true,
+// db.execute("return '杜 一' :: '杜一' > 0.6 as r").next().get("r"));
+// Assert.assertEquals(true,
+// db.execute("return '杜 一' ::jaro '杜一' > 0.6 as r").next().get("r"));
+//
+// db.execute("return '杜 一' ::jaro '杜一','Zhihong SHEN' ::levenshtein 'SHEN Z.H'");
+//
+// tx.success();
+// tx.close();
+// db.shutdown();
+// }
+//
+// @Test
+// def testCustomProperty1(): Unit = {
+// //create a new database
+// val db = openDatabase();
+// val tx = db.beginTx();
+//
+// Assert.assertEquals(new File("./testdata/car1.jpg").length(),
+// db.execute("""return Blob.fromFile('./testdata/car1.jpg')->length as x""")
+// .next().get("x"));
+//
+// Assert.assertEquals("image/jpeg", db.execute("""return Blob.fromFile('./testdata/car1.jpg')->mime as x""")
+// .next().get("x"));
+//
+// Assert.assertEquals(500, db.execute("""return Blob.fromFile('./testdata/car1.jpg')->width as x""")
+// .next().get("x"));
+//
+// Assert.assertEquals(333, db.execute("""return Blob.fromFile('./testdata/car1.jpg')->height as x""")
+// .next().get("x"));
+//
+// Assert.assertEquals(333, db.execute("""return ->height as x""")
+// .next().get("x"));
+//
+// Assert.assertEquals(null, db.execute("""return Blob.fromFile('./testdata/car1.jpg')->notExist as x""")
+// .next().get("x"));
+//
+// tx.success();
+// tx.close();
+// db.shutdown();
+// }
+//
+// @Test
+// def testCustomProperty2(): Unit = {
+// //create a new database
+// val db = openDatabase();
+// val tx = db.beginTx();
+//
+// Assert.assertEquals("苏E730V7", db.execute("""return Blob.fromFile('./testdata/car1.jpg')->plateNumber as r""")
+// .next().get("r"));
+//
+// Assert.assertEquals("我今天早上吃了两个包子", db.execute("""return Blob.fromFile('./testdata/test.wav')->message as r""")
+// .next().get("r").asInstanceOf[Boolean]);
+//
+// tx.success();
+// tx.close();
+// db.shutdown();
+// }
+//}
\ No newline at end of file
diff --git a/itest/src/test/scala/cypher-plus/TestBase.scala b/itest/src/test/scala/cypher-plus/TestBase.scala
new file mode 100644
index 00000000..942e6032
--- /dev/null
+++ b/itest/src/test/scala/cypher-plus/TestBase.scala
@@ -0,0 +1,42 @@
+//import java.io.File
+//
+//import cn.pandadb.blob.Blob
+//import org.apache.commons.io.FileUtils
+//import org.neo4j.graphdb.GraphDatabaseService
+//
+///**
+// * Created by bluejoe on 2019/4/13.
+// */
+//trait TestBase {
+// val testDbDir = new File("./output/testdb");
+// val testConfPath = new File("./testdata/neo4j.conf").getPath;
+//
+// def setupNewDatabase(dbdir: File = testDbDir, conf: String = testConfPath): Unit = {
+// FileUtils.deleteDirectory(dbdir);
+// //create a new database
+// val db = openDatabase(dbdir, conf);
+// val tx = db.beginTx();
+// //create a node
+// val node1 = db.createNode();
+//
+// node1.setProperty("name", "bob");
+// node1.setProperty("age", 40);
+//
+// //with a blob property
+// node1.setProperty("photo", Blob.fromFile(new File("./testdata/test.png")));
+// //blob array
+// node1.setProperty("album", (0 to 5).map(x => Blob.fromFile(new File("./testdata/test.png"))).toArray);
+//
+// val node2 = db.createNode();
+// node2.setProperty("name", "alex");
+// //with a blob property
+// node2.setProperty("photo", Blob.fromFile(new File("./testdata/test1.png")));
+// node2.setProperty("age", 10);
+//
+// //node2.createRelationshipTo(node1, RelationshipType.withName("dad"));
+//
+// tx.success();
+// tx.close();
+// db.shutdown();
+// }
+//}
diff --git a/itest/src/test/scala/distributed/DistributedDataRecoverTest.scala b/itest/src/test/scala/distributed/DistributedDataRecoverTest.scala
new file mode 100644
index 00000000..df3547c4
--- /dev/null
+++ b/itest/src/test/scala/distributed/DistributedDataRecoverTest.scala
@@ -0,0 +1,156 @@
+package distributed
+
+import java.util.concurrent.{ExecutorService, Executors}
+
+import cn.pandadb.network.ZKPathConfig
+import org.apache.curator.framework.{CuratorFramework, CuratorFrameworkFactory}
+import org.apache.curator.retry.ExponentialBackoffRetry
+import org.junit.{Assert, BeforeClass, Test}
+import org.neo4j.driver.{AuthTokens, Driver, GraphDatabase, StatementResult}
+import DistributedDataRecoverTest.{localPNodeServer0, localPNodeServer1, neoDriver0, neoDriver1, pandaDriver, threadPool}
+
+/**
+ * @Author: Airzihao
+ * @Description:
+ * @Date: Created at 17:37 2019/12/4
+ * @Modified By:
+ */
+
+object DistributedDataRecoverTest {
+ @BeforeClass
+ val zkString = "10.0.86.26:2181,10.0.86.27:2181,10.0.86.70:2181"
+ val pandaString = s"panda://" + zkString + "/db"
+ val pandaDriver = GraphDatabase.driver(pandaString, AuthTokens.basic("", ""))
+ val curator: CuratorFramework = CuratorFrameworkFactory.newClient(zkString,
+ new ExponentialBackoffRetry(1000, 3));
+ curator.start()
+
+ val node0 = "159.226.193.204:7684"
+ val zkMasterPath = ZKPathConfig.leaderNodePath + s"/${node0}"
+
+ val node1 = "159.226.193.204:7685";
+ val zkSlavePath = ZKPathConfig.ordinaryNodesPath + s"/${node1}"
+
+ val neoDriver0 = GraphDatabase.driver(s"bolt://${node0}")
+ val neoDriver1 = GraphDatabase.driver(s"bolt://${node1}")
+
+ val localPNodeServer0 = new LocalServerThread(0)
+ val localPNodeServer1 = new LocalServerThread(1)
+ val threadPool: ExecutorService = Executors.newFixedThreadPool(2)
+}
+
+class DistributedDataRecoverTest {
+
+ val time = "12:41"
+ val time2 = "12:42"
+ val time3 = "12:43"
+
+ // only start node0
+ @Test
+ def test1(): Unit = {
+ // no data in cluster
+ threadPool.execute(localPNodeServer0)
+ Thread.sleep(10000)
+
+ _executeStatement(neoDriver0, "Match(n) Delete n;")
+ val result = pandaDriver.session().run("Match(n) Return n;")
+ Assert.assertEquals(false, result.hasNext)
+
+ // create a node
+ _executeStatement(pandaDriver, s"Create(n:Test{time:'${time}'});")
+
+ // query by panda driver
+ val clusterResult = _executeStatement(pandaDriver, "Match(n) Return n;")
+ Assert.assertEquals(time, clusterResult.next().get("n").get("time").asString())
+
+ // query by neo4j driver
+ val nResult1 = _executeStatement(neoDriver0, "Match(n) Return n;")
+ Assert.assertEquals(time.toString, nResult1.next().get("n").get("time").asString())
+
+ // slave node hasn't started.
+ val exists = DistributedDataRecoverTest.curator.checkExists()
+ .forPath(DistributedDataRecoverTest.zkSlavePath)
+ Assert.assertEquals(null, exists)
+ // show the master dataVersionLog
+
+ // slave data is updated
+ threadPool.execute(localPNodeServer1)
+ Thread.sleep(15000)
+// val slaveResult = neoDriver1.session.run("Match(n) Return n;")
+// Assert.assertEquals(time, slaveResult.next().get("n.time").asString())
+ }
+
+ // run the slave node here
+// @Test
+// def test2(): Unit = {
+//
+// }
+
+// @Test
+// def test3(): Unit = {
+// // create a new node
+// DistributedDataRecoverTest.driver.session()
+// .run(s"Create(n:Test2{time2:'${time2}'})")
+//
+// // only one node created.
+// val clusterResult = DistributedDataRecoverTest.driver.session()
+// .run(s"Match(n) Where n.time2='${time2}'")
+// Assert.assertEquals(true, clusterResult.hasNext)
+// Assert.assertEquals(time2, clusterResult.next().get("n.time2"))
+// Assert.assertEquals(false, clusterResult.hasNext)
+// }
+//
+// // close slave here.
+// @Test
+// def test4(): Unit = {
+// DistributedDataRecoverTest.driver.session().run(s"Match(n) Delete n;")
+// DistributedDataRecoverTest.driver.session().run(s"Create(n:Test3);")
+// DistributedDataRecoverTest.driver.session().run(s"Match(n) Set n.time3 = '${time3}';")
+//
+// val clusterResult = DistributedDataRecoverTest.driver.session().run(s"Match(n) Return n;")
+// Assert.assertEquals(time3, clusterResult.next().get("n.time3"))
+// }
+//
+// // close master here.
+// // no server in the cluster now
+// // start slave here.
+// @Test
+// def test5(): Unit = {
+// Assert.assertEquals(null,
+// DistributedDataRecoverTest.curator.checkExists().forPath(DistributedDataRecoverTest.zkSlavePath))
+//
+// Assert.assertEquals(null,
+// DistributedDataRecoverTest.curator.checkExists().forPath(DistributedDataRecoverTest.zkMasterPath))
+// }
+//
+// // start master here.
+// @Test
+// def test6(): Unit = {
+//
+// Assert.assertEquals(false,
+// DistributedDataRecoverTest.curator.checkExists().forPath(DistributedDataRecoverTest.zkSlavePath) == null)
+//
+// Assert.assertEquals(false,
+// DistributedDataRecoverTest.curator.checkExists().forPath(DistributedDataRecoverTest.zkMasterPath) == null)
+//
+// val clusterResult = DistributedDataRecoverTest.driver.session().run(s"Match(n) Return n;")
+// Assert.assertEquals(time3, clusterResult.next().get("n.time3"))
+//
+// val masterResult = GraphDatabase.driver(DistributedDataRecoverTest.node0).session().run(s"Match(n) Return n;")
+// Assert.assertEquals(time3, masterResult.next().get("n.time3"))
+//
+// val slaveResult = GraphDatabase.driver(DistributedDataRecoverTest.node0).session().run(s"Match(n) Return n;")
+// Assert.assertEquals(time3, slaveResult.next().get("n.time3"))
+// }
+
+ private def _executeStatement(driver: Driver, cypher: String): StatementResult = {
+ val session = driver.session()
+ val tx = session.beginTransaction()
+ val result = tx.run(cypher)
+ tx.success()
+ tx.close()
+ session.close()
+ result
+ }
+
+}
diff --git a/itest/src/test/scala/distributed/DriverTest.scala b/itest/src/test/scala/distributed/DriverTest.scala
new file mode 100644
index 00000000..44f9302d
--- /dev/null
+++ b/itest/src/test/scala/distributed/DriverTest.scala
@@ -0,0 +1,102 @@
+package distributed
+
+import java.io.{File, FileInputStream}
+import java.util.Properties
+
+import org.junit.{Assert, Test}
+import org.neo4j.driver.{AuthTokens, GraphDatabase, Transaction, TransactionWork}
+
+/**
+ * Created by bluejoe on 2019/11/21.
+ */
+class DriverTest {
+
+ val configFile = new File("./testdata/gnode0.conf")
+ val props = new Properties()
+ props.load(new FileInputStream(configFile))
+ val pandaString = s"panda://" + props.getProperty("zkServerAddress") + s"/db"
+
+ @Test
+ def test0() {
+ val driver = GraphDatabase.driver(pandaString,
+ AuthTokens.basic("", ""));
+ var results1 = driver.session().run("create (n:person{name:'bluejoe'})");
+ val results = driver.session().run("match (n:person) return n");
+
+ val result = results.next();
+ Assert.assertEquals("bluejoe", result.get("n").asNode().get("name").asString());
+ val results2 = driver.session().run("match (n:person) delete n");
+ driver.close();
+ }
+ @Test
+ def test1() {
+ val driver = GraphDatabase.driver(pandaString,
+ AuthTokens.basic("", ""));
+ val session = driver.session();
+ var results1 = session.run("create (n:person{name:'bluejoe'})");
+ val results = session.run("match (n:person) return n");
+ val result = results.next();
+ Assert.assertEquals("bluejoe", result.get("n").asNode().get("name").asString());
+ val results2 = session.run("match (n:person) delete n");
+ session.close();
+ driver.close();
+ }
+
+ //test transaction
+ @Test
+ def test2() {
+ val driver = GraphDatabase.driver(pandaString,
+ AuthTokens.basic("", ""));
+ val session = driver.session();
+ val transaction = session.beginTransaction()
+ var results1 = transaction.run("create (n:person{name:'bluejoe'})");
+
+ results1 = transaction.run("create (n:people{name:'lin'})");
+ val results = transaction.run("match (n:person) return n.name");
+
+ val result = results.next();
+ Assert.assertEquals("bluejoe", result.get("n.name").asString());
+
+ val results3 = transaction.run("match (n) return n.name");
+ Assert.assertEquals(2, results3.list().size())
+
+ val results2 = transaction.run("match (n) delete n");
+
+ Assert.assertEquals(0, results2.list().size())
+
+ transaction.success()
+ transaction.close()
+ session.close();
+ driver.close();
+ }
+
+ @Test
+ def test3() {
+ val driver = GraphDatabase.driver(pandaString,
+ AuthTokens.basic("", ""));
+ var session = driver.session()
+
+ val result = session.writeTransaction(new TransactionWork[Unit] {
+ override def execute(transaction: Transaction): Unit = {
+ val res1 = transaction.run("create (n:person{name:'bluejoe'})")
+ }
+ })
+ //session = driver.session()
+ val result2 = session.readTransaction(new TransactionWork[Unit] {
+ override def execute(transaction: Transaction): Unit = {
+ val res2 = transaction.run("match (n:person) return n.name")
+ Assert.assertEquals("bluejoe", res2.next().get("n.name").asString());
+ }
+ })
+ //session = driver.session()
+ val result3 = session.writeTransaction(new TransactionWork[Unit] {
+ override def execute(transaction: Transaction): Unit = {
+ val res3 = transaction.run("match (n) delete n")
+ //Assert.assertEquals("bluejoe", res2.next().get("name").asString());
+ }
+ })
+ session.close();
+ driver.close();
+ }
+
+}
diff --git a/itest/src/test/scala/distributed/LocalDataVersionRecoveryTest.scala b/itest/src/test/scala/distributed/LocalDataVersionRecoveryTest.scala
new file mode 100644
index 00000000..2145a5b3
--- /dev/null
+++ b/itest/src/test/scala/distributed/LocalDataVersionRecoveryTest.scala
@@ -0,0 +1,71 @@
+package distributed
+
+import java.io.{File, FileInputStream}
+import java.util.Properties
+import java.util.concurrent.{ExecutorService, Executors}
+
+import cn.pandadb.network.NodeAddress
+import cn.pandadb.server.{DataVersionRecoveryArgs, LocalDataVersionRecovery}
+import distributed.LocalDataVersionRecoveryTest.{neodriver, recovery}
+import org.junit.{Assert, BeforeClass, Test}
+import org.neo4j.driver.{Driver, GraphDatabase}
+
+/**
+ * @Author: Airzihao
+ * @Description:
+ * @Date: Created at 16:18 2019/12/3
+ * @Modified By:
+ */
+object LocalDataVersionRecoveryTest {
+
+ val localLogFile = new File("./src/test/resources/localLog.json")
+ val clusterLogFile = new File("./src/test/resources/clusterLog.json")
+
+ val localPNodeServer = new LocalServerThread(0)
+
+ val confFile = new File("../itest/testdata/localnode0.conf")
+ val props = new Properties()
+ props.load(new FileInputStream(confFile))
+ val localNodeAddress = NodeAddress.fromString(props.getProperty("node.server.address"))
+
+ val recoveryArgs = DataVersionRecoveryArgs(localLogFile, clusterLogFile, localNodeAddress)
+ val recovery = new LocalDataVersionRecovery(recoveryArgs)
+
+ val neodriver: Driver = {
+ GraphDatabase.driver(s"bolt://" + localNodeAddress.getAsString)
+ }
+
+ @BeforeClass
+ private def _startServer(): Unit = {
+ val threadPool: ExecutorService = Executors.newFixedThreadPool(1)
+ threadPool.execute(localPNodeServer)
+ Thread.sleep(10000)
+ }
+ _startServer()
+
+}
+
+class LocalDataVersionRecoveryTest {
+
+ @Test
+ def test1(): Unit = {
+ val _session = neodriver.session()
+ val beforeResult = _session.run("Match(n) Return(n)")
+ Assert.assertEquals(false, beforeResult.hasNext)
+ _session.close()
+ }
+
+ @Test
+ def test2(): Unit = {
+ recovery.updateLocalVersion()
+ val _session = neodriver.session()
+ val afterResult = _session.run("Match(n) return n;")
+ Assert.assertEquals(true, afterResult.hasNext)
+
+ while (afterResult.hasNext) {
+ Assert.assertEquals(3.toInt, afterResult.next().get("n").asMap().get("version").toString.toInt)
+ }
+ _session.run("Match(n) Delete n")
+ }
+
+}
diff --git a/itest/src/test/scala/distributed/LocalMultiThreadLauncher.scala b/itest/src/test/scala/distributed/LocalMultiThreadLauncher.scala
new file mode 100644
index 00000000..a9da2023
--- /dev/null
+++ b/itest/src/test/scala/distributed/LocalMultiThreadLauncher.scala
@@ -0,0 +1,18 @@
+package distributed
+
+import java.io.File
+
+import cn.pandadb.server.PNodeServer
+/**
+ * @Author: Airzihao
+ * @Description:
+ * @Date: Created at 8:59 2019/12/24
+ * @Modified By:
+ */
+
+class LocalServerThread(num: Int) extends Runnable {
+ override def run(): Unit = {
+ PNodeServer.startServer(new File(s"../itest/output/testdb/db${num}"),
+ new File(s"../itest/testdata/localnode${num}.conf"))
+ }
+}
\ No newline at end of file
diff --git a/itest/src/test/scala/distributed/PNodeServerStarterTest.scala b/itest/src/test/scala/distributed/PNodeServerStarterTest.scala
new file mode 100644
index 00000000..2856128e
--- /dev/null
+++ b/itest/src/test/scala/distributed/PNodeServerStarterTest.scala
@@ -0,0 +1,14 @@
+package distributed
+
+import cn.pandadb.tool.PNodeServerStarter
+
+/**
+ * Created by bluejoe on 2019/11/24.
+ */
+object PNodeServerStarterTest {
+ def main(args: Array[String]) {
+ val num = 0
+ PNodeServerStarter.main(Array(s"./itest/output/testdb/db${num}",
+ s"./itest/testdata/localnode${num}.conf"));
+ }
+}
\ No newline at end of file
diff --git a/itest/src/test/scala/distributed/PandaDriverTest.scala b/itest/src/test/scala/distributed/PandaDriverTest.scala
new file mode 100644
index 00000000..d6f17969
--- /dev/null
+++ b/itest/src/test/scala/distributed/PandaDriverTest.scala
@@ -0,0 +1,175 @@
+package distributed
+
+import java.io.{File, FileInputStream}
+import java.util.{Locale, Properties}
+
+import cn.pandadb.driver.PandaDriver
+import cn.pandadb.network.ZKPathConfig
+import distributed.PandaDriverTest.{neoDriver0, neoDriver1, pandaDriver}
+import org.apache.curator.framework.CuratorFrameworkFactory
+import org.apache.curator.retry.ExponentialBackoffRetry
+import org.junit.runners.MethodSorters
+import org.junit._
+import org.neo4j.driver.{AuthTokens, GraphDatabase}
+
+/**
+ * @Author: Airzihao
+ * @Description:
+ * @Date: Created at 16:58 2019/12/7
+ * @Modified By:
+ */
+
+// todo: test n.prop
+object PandaDriverTest {
+ val configFile = new File("./testdata/gnode0.conf")
+ val props = new Properties()
+ props.load(new FileInputStream(configFile))
+ val pandaString = s"panda://" + props.getProperty("zkServerAddress") + s"/db"
+
+ ZKPathConfig.initZKPath(props.getProperty("zkServerAddress"))
+ // correct these two addresses please
+ val node0 = "bolt://localhost:7684"
+ val node1 = "bolt://localhost:7685"
+
+ val pandaDriver = GraphDatabase.driver(pandaString, AuthTokens.basic("", ""))
+ val neoDriver0 = GraphDatabase.driver(node0, AuthTokens.basic("", ""))
+ val neoDriver1 = GraphDatabase.driver(node1, AuthTokens.basic("", ""))
+
+ @BeforeClass
+ def deleteAllData(): Unit = {
+ pandaDriver.session().run("Match(n) Delete n;")
+ Thread.sleep(1500)
+ }
+
+ @AfterClass
+ def deleteVersion(): Unit = {
+ pandaDriver.session().run("Match(n) Delete n;")
+ val curator = CuratorFrameworkFactory.newClient(props.getProperty("zkServerAddress"),
+ new ExponentialBackoffRetry(1000, 3))
+ curator.start()
+ curator.delete().forPath("/testPandaDB/version")
+ curator.close()
+ }
+}
+
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class PandaDriverTest {
+
+ // check the type
+ @Test
+ def test0(): Unit = {
+ // scalastyle:off
+ Assert.assertEquals(true, pandaDriver.isInstanceOf[PandaDriver])
+ Assert.assertEquals("class org.neo4j.driver.internal.InternalDriver", neoDriver0.getClass.toString)
+ Assert.assertEquals("class org.neo4j.driver.internal.InternalDriver", neoDriver1.getClass.toString)
+ }
+
+ // make sure the database is blank
+ @Test
+ def test1(): Unit = {
+ val session = pandaDriver.session()
+ val tx = session.beginTransaction()
+ tx.run("Match(n) Delete n;")
+ tx.success()
+ tx.close()
+ session.close()
+ val clusterResult = pandaDriver.session().run("Match(n) Return n;")
+ val node0Result = neoDriver0.session().run("Match(n) Return n;")
+ val node1Result = neoDriver1.session().run("Match(n) Return n;")
+ Assert.assertEquals(false, clusterResult.hasNext)
+ Assert.assertEquals(false, node0Result.hasNext)
+ Assert.assertEquals(false, node1Result.hasNext)
+ }
+
+ @Test
+ def test2(): Unit = {
+ val session = pandaDriver.session()
+ val tx = session.beginTransaction()
+ tx.run("Create(n:Test{prop:'panda'})")
+ tx.success()
+ tx.close()
+ session.close()
+ _createAndMerge()
+ }
+
+ @Test
+ def test3(): Unit = {
+ val session = pandaDriver.session()
+ val tx = session.beginTransaction()
+ tx.run("Merge(n:Test{prop:'panda'})")
+ tx.success()
+ tx.close()
+ session.close()
+ _createAndMerge()
+ }
+
+ @Test
+ def test4(): Unit = {
+
+ val session = pandaDriver.session()
+ val tx = session.beginTransaction()
+ tx.run("Create(n:Test{prop:'panda'})")
+ tx.success()
+ tx.close()
+ session.close()
+
+ val session1 = pandaDriver.session()
+ val tx1 = session1.beginTransaction()
+ tx1.run("Merge(n:Test{prop:'panda'})")
+ tx1.close()
+ session1.close()
+ _createAndMerge()
+ }
+
+ @Test
+ def test5(): Unit = {
+ val session = pandaDriver.session()
+ val tx = session.beginTransaction()
+ tx.run("Create(n:Test)")
+ tx.success()
+ tx.close()
+ session.close()
+
+ val session1 = pandaDriver.session()
+ val tx1 = session1.beginTransaction()
+ tx1.run("Match(n) Set n.prop='panda'")
+ tx1.success()
+ tx1.close()
+ session1.close()
+
+ _createAndMerge()
+ }
+
+ //add a test, to test different menthod to run statement.
+ private def _createAndMerge(): Unit = {
+ // Problem: the result is not available real-time.
+ val clusterResult = pandaDriver.session().run("Match(n) Return n")
+ val node0Result = neoDriver0.session().run("Match(n) Return n")
+ val node1Result = neoDriver1.session().run("Match(n) Return n")
+ Assert.assertEquals(true, clusterResult.hasNext)
+ Assert.assertEquals("panda", clusterResult.next().get("n").asNode().get("prop").asString())
+ Assert.assertEquals(false, clusterResult.hasNext)
+
+ Assert.assertEquals(true, node0Result.hasNext)
+ Assert.assertEquals("panda", node0Result.next().get("n").asNode().get("prop").asString())
+ Assert.assertEquals(false, node0Result.hasNext)
+
+ Assert.assertEquals(true, node1Result.hasNext)
+ Assert.assertEquals("panda", node1Result.next().get("n").asNode().get("prop").asString())
+ Assert.assertEquals(false, node1Result.hasNext)
+
+ val session = pandaDriver.session()
+ val tx = session.beginTransaction()
+ tx.run("Match(n) Delete n;")
+ tx.success()
+ tx.close()
+ session.close()
+ val clusterResult1 = pandaDriver.session().run("Match(n) Return n")
+ val node0Result1 = neoDriver0.session().run("Match(n) Return n")
+ val node1Result1 = neoDriver1.session().run("Match(n) Return n")
+ Assert.assertEquals(false, clusterResult1.hasNext)
+ Assert.assertEquals(false, node0Result1.hasNext)
+ Assert.assertEquals(false, node1Result1.hasNext)
+ }
+
+}
diff --git a/itest/src/test/scala/distributed/StatesTest.scala b/itest/src/test/scala/distributed/StatesTest.scala
new file mode 100644
index 00000000..47c6d00e
--- /dev/null
+++ b/itest/src/test/scala/distributed/StatesTest.scala
@@ -0,0 +1,17 @@
+package distributed
+
+class StatesTest {
+/*
+ test whether the flag is right in different state and the system behave as supposed.
+ State List:
+ Serving(locked/locked) <--
+ PreWrite |
+ Write |
+ Finish ----
+ Flag:
+ READY_TO_WRITE true, when there is no served request
+ WRITE_TO_FINISHED true, when all the gnodes finish the write operation
+ WAIT_JOIN (only active in serving state) true, if there is a gnode waits for joining in the cluster
+
+ */
+}
diff --git a/itest/src/test/scala/distributed/WriteTest.scala b/itest/src/test/scala/distributed/WriteTest.scala
new file mode 100644
index 00000000..9b89779b
--- /dev/null
+++ b/itest/src/test/scala/distributed/WriteTest.scala
@@ -0,0 +1,37 @@
+package distributed
+
+import org.junit.Test
+import org.neo4j.driver.{AuthTokens, Driver, GraphDatabase, StatementResult}
+
+import scala.collection.mutable.ListBuffer
+import scala.concurrent.Future
+
+/**
+ * @Author: Airzihao
+ * @Description:
+ * @Date: Created at 13:41 2019/11/30
+ * @Modified By:
+ */
+class WriteTest {
+ val pandaDriver: Driver = GraphDatabase.driver("panda://10.0.86.26:2181/db", AuthTokens.basic("", ""))
+ val cypherList = List("Create(n:Test{name:'alice'});", "Create(n:Test{age:10});", "Merge(n:Test{name:'alice'});")
+//
+ @Test
+ def test1(): Unit = {
+ val taskList: ListBuffer[Future[StatementResult]] = new ListBuffer[Future[StatementResult]]
+ cypherList.foreach(cypher => {
+ execute(pandaDriver, cypher)
+ })
+
+ }
+
+ def execute(driver: Driver, cypher: String): StatementResult = {
+ val session = driver.session()
+ val tx = session.beginTransaction()
+ val statementResult = tx.run(cypher)
+ tx.success()
+ tx.close()
+ session.close()
+ statementResult
+ }
+}
diff --git a/itest/src/test/scala/external-properties/InEsPropertyTest.scala b/itest/src/test/scala/external-properties/InEsPropertyTest.scala
new file mode 100644
index 00000000..e53021c4
--- /dev/null
+++ b/itest/src/test/scala/external-properties/InEsPropertyTest.scala
@@ -0,0 +1,195 @@
+
+
+package Externalproperties
+
+import java.io.{File, FileInputStream}
+import java.util.Properties
+
+import scala.collection.JavaConversions._
+import org.apache.http.HttpHost
+import org.junit.{Assert, Test}
+import org.elasticsearch.client.{RequestOptions, RestClient, RestHighLevelClient}
+import org.elasticsearch.action.admin.indices.create.{CreateIndexRequest, CreateIndexResponse}
+import org.elasticsearch.action.index.{IndexRequest, IndexResponse}
+import org.elasticsearch.common.xcontent.XContentType
+import org.elasticsearch.action.delete.{DeleteRequest, DeleteResponse}
+import org.elasticsearch.action.get.{GetRequest, GetResponse}
+import org.elasticsearch.action.update.{UpdateRequest, UpdateResponse}
+import org.elasticsearch.common.xcontent.XContentType
+import org.elasticsearch.common.Strings
+import org.elasticsearch.index.query.{QueryBuilder, QueryBuilders}
+import org.elasticsearch.search.fetch.subphase.FetchSourceContext
+import org.elasticsearch.search.builder.SearchSourceBuilder
+import org.elasticsearch.action.search.{SearchRequest, SearchResponse}
+import org.elasticsearch.index.reindex.{BulkByScrollResponse, DeleteByQueryRequest}
+import org.elasticsearch.script.ScriptType
+import org.elasticsearch.script.mustache.SearchTemplateRequest
+import org.elasticsearch.client.RequestOptions
+import org.elasticsearch.script.mustache.SearchTemplateResponse
+import cn.pandadb.externalprops.{InElasticSearchPropertyNodeStore, NodeWithProperties}
+import org.neo4j.values.storable.Values
+import com.alibaba.fastjson.JSONObject
+
+/**
+ * Created by codeBabyLin on 2019/12/5.
+ */
+
+class InEsPropertyTest {
+ val host = "10.0.82.216"
+ val port = 9200
+ val indexName = "test0113"
+ val typeName = "doc"
+
+ val httpHost = new HttpHost(host, port, "http")
+ val builder = RestClient.builder(httpHost)
+ val client = new RestHighLevelClient(builder)
+
+ @Test
+ def test1(): Unit = {
+ val esNodeStore = new InElasticSearchPropertyNodeStore(host, port, indexName, typeName)
+ esNodeStore.clearAll()
+ Assert.assertEquals(0, esNodeStore.getRecorderSize)
+ var transaction = esNodeStore.beginWriteTransaction()
+ transaction.addNode(1)
+ transaction.addLabel(1, "person")
+ transaction.addLabel(1, "people")
+ transaction.commit()
+ transaction.close()
+ Assert.assertEquals(1, esNodeStore.getRecorderSize)
+ transaction = esNodeStore.beginWriteTransaction()
+ var label = transaction.getNodeLabels(1)
+ Assert.assertEquals(2, label.size)
+ Assert.assertEquals("person", label.head)
+ Assert.assertEquals("people", label.last)
+ transaction.removeLabel(1, "people")
+ label = transaction.getNodeLabels(1)
+ Assert.assertEquals(1, label.size)
+ Assert.assertEquals("person", label.head)
+ transaction.commit()
+ transaction.close()
+ esNodeStore.clearAll()
+ Assert.assertEquals(0, esNodeStore.getRecorderSize)
+ }
+
+ // test for node property add and remove
+ @Test
+ def test2() {
+ val esNodeStore = new InElasticSearchPropertyNodeStore(host, port, indexName, typeName)
+ esNodeStore.clearAll()
+ Assert.assertEquals(0, esNodeStore.getRecorderSize)
+ var transaction = esNodeStore.beginWriteTransaction()
+ transaction.addNode(1)
+ transaction.addProperty(1, "database", Values.of("pandaDB"))
+
+ transaction.commit()
+
+ transaction.close()
+ Assert.assertEquals(1, esNodeStore.getRecorderSize)
+ transaction = esNodeStore.beginWriteTransaction()
+ val name = transaction.getPropertyValue(1, "database")
+ Assert.assertEquals("pandaDB", name.get.asObject())
+
+ transaction.removeProperty(1, "database")
+ transaction.commit()
+ transaction.close()
+ Assert.assertEquals(1, esNodeStore.getRecorderSize)
+
+ val node = esNodeStore.getNodeById(1).head.mutable()
+ Assert.assertEquals(true, node.props.isEmpty)
+
+ esNodeStore.clearAll()
+ Assert.assertEquals(0, esNodeStore.getRecorderSize)
+ }
+
+ //test for undo
+ @Test
+ def test3() {
+ val esNodeStore = new InElasticSearchPropertyNodeStore(host, port, indexName, typeName)
+ esNodeStore.clearAll()
+ Assert.assertEquals(0, esNodeStore.getRecorderSize)
+ var transaction = esNodeStore.beginWriteTransaction()
+ transaction.addNode(1)
+ transaction.addNode(2)
+ transaction.addLabel(1, "person")
+ transaction.addProperty(2, "name", Values.of("pandaDB"))
+ val undo = transaction.commit()
+ Assert.assertEquals(2, esNodeStore.getRecorderSize)
+ val node1 = esNodeStore.getNodeById(1)
+ val node2 = esNodeStore.getNodeById(2)
+ Assert.assertEquals("person", node1.head.mutable().labels.head)
+ Assert.assertEquals("pandaDB", node2.head.mutable().props.get("name").get.asObject())
+ undo.undo()
+ transaction.close()
+ Assert.assertEquals(0, esNodeStore.getRecorderSize)
+ }
+
+ // test for node property value
+ @Test
+ def test4() {
+ val esNodeStore = new InElasticSearchPropertyNodeStore(host, port, indexName, typeName)
+ esNodeStore.clearAll()
+ Assert.assertEquals(0, esNodeStore.getRecorderSize)
+ var transaction = esNodeStore.beginWriteTransaction()
+ transaction.addNode(1)
+ transaction.addLabel(1, "Person")
+ transaction.addLabel(1, "Man")
+ transaction.addProperty(1, "name", Values.of("test"))
+ transaction.addProperty(1, "arr1", Values.of(100))
+ transaction.addProperty(1, "arr2", Values.of(155.33))
+ transaction.addProperty(1, "arr3", Values.of(true))
+ transaction.addProperty(1, "arr4", Values.of(Array(1, 2, 3)))
+ Assert.assertEquals(0, esNodeStore.getRecorderSize)
+ transaction.commit()
+ transaction.close()
+ Assert.assertEquals(1, esNodeStore.getRecorderSize)
+ val node: NodeWithProperties = esNodeStore.getNodeById(1).get
+ Assert.assertEquals(1, node.id)
+ val nodeLabels = node.labels.toArray
+ Assert.assertEquals(2, nodeLabels.size)
+ Assert.assertEquals(true, nodeLabels.contains("Man") && nodeLabels.contains("Person"))
+ assert(node.props.get("name").get.equals("test"))
+ assert(node.props.get("arr1").get.equals(100))
+ assert(node.props.get("arr2").get.equals(155.33))
+ assert(node.props.get("arr3").get.equals(true))
+ assert(node.props.get("arr4").get.equals(Array(1, 2, 3)))
+ }
+
+ // test for serach
+ @Test
+ def test5() {
+ val esNodeStore = new InElasticSearchPropertyNodeStore(host, port, indexName, typeName)
+ esNodeStore.clearAll()
+ Assert.assertEquals(0, esNodeStore.getRecorderSize)
+ var transaction = esNodeStore.beginWriteTransaction()
+ transaction.addNode(1)
+ transaction.addLabel(1, "Person")
+ transaction.addLabel(1, "Man")
+ transaction.addProperty(1, "name", Values.of("test"))
+ transaction.addProperty(1, "arr1", Values.of(100))
+ transaction.addProperty(1, "arr2", Values.of(155.33))
+ transaction.addProperty(1, "arr3", Values.of(true))
+ transaction.addProperty(1, "arr4", Values.of(Array(1, 2, 3)))
+ Assert.assertEquals(0, esNodeStore.getRecorderSize)
+ transaction.commit()
+ transaction.close()
+
+ Assert.assertEquals(1, esNodeStore.getRecorderSize)
+
+ val nodeIds = esNodeStore.filterNodes(QueryBuilders.termQuery("name", "test"))
+ assert(nodeIds.size == 1)
+ nodeIds.foreach(id => assert(id == 1))
+
+ val nodesWithProps = esNodeStore.filterNodesWithProperties(QueryBuilders.termQuery("name", "test"))
+
+ val node: NodeWithProperties = nodesWithProps.toList(0)
+ Assert.assertEquals(1, node.id)
+ val nodeLabels = node.labels.toArray
+ Assert.assertEquals(2, nodeLabels.size)
+ Assert.assertEquals(true, nodeLabels.contains("Man") && nodeLabels.contains("Person"))
+ assert(node.props.get("name").get.equals("test"))
+ assert(node.props.get("arr1").get.equals(100))
+ assert(node.props.get("arr2").get.equals(155.33))
+ assert(node.props.get("arr3").get.equals(true))
+ assert(node.props.get("arr4").get.equals(Array(1, 2, 3)))
+ }
+}
diff --git a/itest/src/test/scala/external-properties/InMemPropertyTest.scala b/itest/src/test/scala/external-properties/InMemPropertyTest.scala
new file mode 100644
index 00000000..300b48c9
--- /dev/null
+++ b/itest/src/test/scala/external-properties/InMemPropertyTest.scala
@@ -0,0 +1,111 @@
+package Externalproperties
+
+import java.io.{File, FileInputStream}
+import java.util.Properties
+
+import cn.pandadb.externalprops.InMemoryPropertyNodeStore
+import org.junit.{Assert, Test}
+import org.neo4j.driver.{AuthTokens, GraphDatabase, Transaction, TransactionWork}
+import org.neo4j.values.AnyValues
+import org.neo4j.values.storable.Values
+
+class InMemPropertyTest {
+
+ //test node add 、delete,and property add and remove
+ @Test
+ def test1() {
+ InMemoryPropertyNodeStore.nodes.clear()
+ Assert.assertEquals(0, InMemoryPropertyNodeStore.nodes.size)
+ var transaction = InMemoryPropertyNodeStore.beginWriteTransaction()
+ transaction.addNode(1)
+ transaction.commit()
+ Assert.assertEquals(1, InMemoryPropertyNodeStore.nodes.size)
+ transaction = InMemoryPropertyNodeStore.beginWriteTransaction()
+ transaction.addProperty(1, "name", Values.of("bluejoe"))
+ var name = transaction.getPropertyValue(1, "name")
+ Assert.assertEquals("bluejoe", name.get.asObject())
+
+ transaction.removeProperty(1, "name")
+ name = transaction.getPropertyValue(1, "name")
+ Assert.assertEquals(None, name)
+
+ transaction.deleteNode(1)
+ transaction.commit()
+ transaction.close()
+ Assert.assertEquals(0, InMemoryPropertyNodeStore.nodes.size)
+
+ }
+
+ //test label add and removed
+ @Test
+ def test2() {
+ InMemoryPropertyNodeStore.nodes.clear()
+ Assert.assertEquals(0, InMemoryPropertyNodeStore.nodes.size)
+ val transaction = InMemoryPropertyNodeStore.beginWriteTransaction()
+ transaction.addNode(1)
+ transaction.addLabel(1, "person")
+ var label = transaction.getNodeLabels(1)
+
+ Assert.assertEquals("person", label.head)
+
+ transaction.removeLabel(1, "person")
+ label = transaction.getNodeLabels(1)
+ Assert.assertEquals(true, label.isEmpty)
+ transaction.deleteNode(1)
+ transaction.commit()
+ transaction.close()
+ Assert.assertEquals(0, InMemoryPropertyNodeStore.nodes.size)
+
+ }
+
+ @Test
+ def test3() {
+ InMemoryPropertyNodeStore.nodes.clear()
+ Assert.assertEquals(0, InMemoryPropertyNodeStore.nodes.size)
+ val transaction = InMemoryPropertyNodeStore.beginWriteTransaction()
+ transaction.addNode(1)
+ transaction.addLabel(1, "person")
+ var label = transaction.getNodeLabels(1)
+
+ Assert.assertEquals("person", label.head)
+
+ val redo = transaction.commit()
+
+ Assert.assertEquals(1, InMemoryPropertyNodeStore.nodes.size)
+ Assert.assertEquals("person", InMemoryPropertyNodeStore.nodes.get(1).get.mutable().labels.head)
+
+ redo.undo()
+ transaction.commit()
+ redo.undo()
+ transaction.close()
+
+ Assert.assertEquals(0, InMemoryPropertyNodeStore.nodes.size)
+
+ }
+
+ //test label add
+ @Test
+ def test4() {
+ InMemoryPropertyNodeStore.nodes.clear()
+ Assert.assertEquals(0, InMemoryPropertyNodeStore.nodes.size)
+ val transaction = InMemoryPropertyNodeStore.beginWriteTransaction()
+ transaction.addNode(1)
+ transaction.addLabel(1, "person")
+ var label = transaction.getNodeLabels(1)
+
+ Assert.assertEquals("person", label.head)
+
+ transaction.addLabel(1, "Man")
+ label = transaction.getNodeLabels(1)
+ assert(label.size == 2 && label.contains("Man") && label.contains("person"))
+
+ transaction.commit()
+ transaction.close()
+
+ Assert.assertEquals(1, InMemoryPropertyNodeStore.nodes.size)
+ val node = InMemoryPropertyNodeStore.getNodeById(1)
+ val labels = node.get.mutable().labels
+ assert(labels.size == 2 && labels.contains("person") && labels.contains("Man"))
+ }
+
+}
diff --git a/itest/src/test/scala/external-properties/InSolrArrayTest.scala b/itest/src/test/scala/external-properties/InSolrArrayTest.scala
new file mode 100644
index 00000000..ee68bf3f
--- /dev/null
+++ b/itest/src/test/scala/external-properties/InSolrArrayTest.scala
@@ -0,0 +1,121 @@
+
+
+package Externalproperties
+
+import java.io.{File, FileInputStream}
+import java.util.Properties
+
+import cn.pandadb.externalprops._
+import cn.pandadb.server.PNodeServer
+import cn.pandadb.util.GlobalContext
+import org.junit.{After, Assert, Before, Test}
+import org.neo4j.driver.{AuthTokens, GraphDatabase, Transaction, TransactionWork}
+import org.neo4j.graphdb.GraphDatabaseService
+import org.neo4j.graphdb.factory.GraphDatabaseFactory
+import org.neo4j.io.fs.FileUtils
+import org.neo4j.values.{AnyValue, AnyValues}
+import org.neo4j.values.storable.{BooleanArray, LongArray, StringArray, Values}
+
+/**
+ * Created by codeBabyLin on 2019/12/5.
+ */
+trait QueryTestBase {
+ var db: GraphDatabaseService = null
+ val nodeStore = "InSolrPropertyNodeStore"
+
+ @Before
+ def initdb(): Unit = {
+ PNodeServer.toString
+ new File("./output/testdb").mkdirs();
+ FileUtils.deleteRecursively(new File("./output/testdb"));
+ db = new GraphDatabaseFactory().newEmbeddedDatabaseBuilder(new File("./output/testdb")).
+ newGraphDatabase()
+ nodeStore match {
+ case "InMemoryPropertyNodeStore" =>
+ InMemoryPropertyNodeStore.nodes.clear()
+ ExternalPropertiesContext.bindCustomPropertyNodeStore(InMemoryPropertyNodeStore)
+
+ case "InSolrPropertyNodeStore" =>
+ val configFile = new File("./testdata/neo4j.conf")
+ val props = new Properties()
+ props.load(new FileInputStream(configFile))
+ val zkString = props.getProperty("external.properties.store.solr.zk")
+ val collectionName = props.getProperty("external.properties.store.solr.collection")
+ val solrNodeStore = new InSolrPropertyNodeStore(zkString, collectionName)
+ solrNodeStore.clearAll()
+ ExternalPropertiesContext.bindCustomPropertyNodeStore(solrNodeStore)
+ GlobalContext.setLeaderNode(true)
+ }
+ }
+
+ @After
+ def shutdowndb(): Unit = {
+ db.shutdown()
+ }
+
+ protected def testQuery[T](query: String): Unit = {
+ val tx = db.beginTx();
+ val rs = db.execute(query);
+ while (rs.hasNext) {
+ val row = rs.next();
+ }
+ tx.success();
+ tx.close()
+ }
+}
+trait CreateQueryTestBase extends QueryTestBase {
+
+}
+
+class InSolrArrayTest extends CreateQueryTestBase {
+
+ val configFile = new File("./testdata/neo4j.conf")
+ val props = new Properties()
+ props.load(new FileInputStream(configFile))
+ val zkString = props.getProperty("external.properties.store.solr.zk")
+ val collectionName = props.getProperty("external.properties.store.solr.collection")
+
+ //val collectionName = "test"
+
+ //test for node label add and remove
+ @Test
+ def test1() {
+ val solrNodeStore = new InSolrPropertyNodeStore(zkString, collectionName)
+ solrNodeStore.clearAll()
+ Assert.assertEquals(0, solrNodeStore.getRecorderSize)
+
+ // create node with Array type property value
+ val query =
+ """CREATE (n1:Person { name:'test01',titles:["ceo","ui","dev"],
+ |salaries:[10000,20000,30597,500954], boolattr:[False,True,false,true]})
+ |RETURN id(n1)
+ """.stripMargin
+ val rs = db.execute(query)
+ var id1: Long = -1
+ if (rs.hasNext) {
+ val row = rs.next ()
+ id1 = row.get("id(n1)").toString.toLong
+ }
+ Assert.assertEquals(1, solrNodeStore.getRecorderSize)
+ val res = solrNodeStore.getNodeById(0)
+ val titles = Array("ceo", "ui", "dev")
+ val salaries = Array(10000, 20000, 30597, 500954)
+ val boolattr = Array(false, true, false, true)
+
+ //scalastyle:off println
+
+ println(res)
+ // println(res.get.props.get("titles").get.asObject().getClass)
+ // println(res.get.props.get("salaries").get.asObject().getClass)
+ // println(res.get.props.get("boolattr").get.asObject().getClass)
+ // Assert.assertEquals(3, res.get.props.get("titles").get.asInstanceOf[StringArray].length())
+ Assert.assertEquals(4, res.get.props.get("salaries").get.asInstanceOf[LongArray].length())
+ Assert.assertEquals(4, res.get.props.get("boolattr").get.asInstanceOf[BooleanArray].length())
+ //Assert.assertEquals(boolattr, res.get.props.get("boolattr").get.asObject())
+ //assert(res.get.props.get("titles").equals(titles))
+ //assert(res.get.props.get("salaries").equals(salaries))
+ //assert(res.get.props.get("boolattr").equals(boolattr))
+
+ }
+
+}
diff --git a/itest/src/test/scala/external-properties/InSolrPropertyTest.scala b/itest/src/test/scala/external-properties/InSolrPropertyTest.scala
new file mode 100644
index 00000000..d84b5961
--- /dev/null
+++ b/itest/src/test/scala/external-properties/InSolrPropertyTest.scala
@@ -0,0 +1,109 @@
+
+
+package Externalproperties
+
+import java.io.{File, FileInputStream}
+import java.util.Properties
+
+import cn.pandadb.externalprops.{InMemoryPropertyNodeStore, InSolrPropertyNodeStore, MutableNodeWithProperties, NodeWithProperties}
+import org.junit.{Assert, Test}
+import org.neo4j.driver.{AuthTokens, GraphDatabase, Transaction, TransactionWork}
+import org.neo4j.values.{AnyValue, AnyValues}
+import org.neo4j.values.storable.Values
+
+/**
+ * Created by codeBabyLin on 2019/12/5.
+ */
+
+class InSolrPropertyTest {
+
+ val configFile = new File("./testdata/neo4j.conf")
+ val props = new Properties()
+ props.load(new FileInputStream(configFile))
+ val zkString = props.getProperty("external.properties.store.solr.zk")
+ val collectionName = props.getProperty("external.properties.store.solr.collection")
+
+ //val collectionName = "test"
+
+//test for node label add and remove
+ @Test
+ def test1() {
+ val solrNodeStore = new InSolrPropertyNodeStore(zkString, collectionName)
+ solrNodeStore.clearAll()
+ Assert.assertEquals(0, solrNodeStore.getRecorderSize)
+ var transaction = solrNodeStore.beginWriteTransaction()
+ transaction.addNode(1)
+ transaction.addLabel(1, "person")
+ transaction.addLabel(1, "people")
+ transaction.commit()
+ transaction.close()
+ Assert.assertEquals(1, solrNodeStore.getRecorderSize)
+ transaction = solrNodeStore.beginWriteTransaction()
+ var label = transaction.getNodeLabels(1)
+ Assert.assertEquals(2, label.size)
+ Assert.assertEquals("person", label.head)
+ Assert.assertEquals("people", label.last)
+ transaction.removeLabel(1, "people")
+ label = transaction.getNodeLabels(1)
+ Assert.assertEquals(1, label.size)
+ Assert.assertEquals("person", label.head)
+ transaction.commit()
+ transaction.close()
+ solrNodeStore.clearAll()
+ Assert.assertEquals(0, solrNodeStore.getRecorderSize)
+ }
+
+// test for node property add and remove
+ @Test
+ def test2() {
+ val solrNodeStore = new InSolrPropertyNodeStore(zkString, collectionName)
+ solrNodeStore.clearAll()
+ Assert.assertEquals(0, solrNodeStore.getRecorderSize)
+ var transaction = solrNodeStore.beginWriteTransaction()
+ transaction.addNode(1)
+ transaction.addProperty(1, "database", Values.of("pandaDB"))
+
+ transaction.commit()
+
+ transaction.close()
+ Assert.assertEquals(1, solrNodeStore.getRecorderSize)
+ transaction = solrNodeStore.beginWriteTransaction()
+ val name = transaction.getPropertyValue(1, "database")
+ Assert.assertEquals("pandaDB", name.get.asObject())
+
+ transaction.removeProperty(1, "database")
+ transaction.commit()
+ transaction.close()
+ Assert.assertEquals(1, solrNodeStore.getRecorderSize)
+
+ val node = solrNodeStore.getNodeById(1).head.mutable()
+ Assert.assertEquals(true, node.props.isEmpty)
+
+ solrNodeStore.clearAll()
+ Assert.assertEquals(0, solrNodeStore.getRecorderSize)
+ }
+
+ //test for undo
+ @Test
+ def test3() {
+ val solrNodeStore = new InSolrPropertyNodeStore(zkString, collectionName)
+ solrNodeStore.clearAll()
+ Assert.assertEquals(0, solrNodeStore.getRecorderSize)
+ var transaction = solrNodeStore.beginWriteTransaction()
+ transaction.addNode(1)
+ transaction.addNode(2)
+ transaction.addLabel(1, "person")
+ transaction.addProperty(2, "name", Values.of("pandaDB"))
+ val undo = transaction.commit()
+ Assert.assertEquals(2, solrNodeStore.getRecorderSize)
+ val node1 = solrNodeStore.getNodeById(1)
+ val node2 = solrNodeStore.getNodeById(2)
+ Assert.assertEquals("person", node1.head.mutable().labels.head)
+ Assert.assertEquals("pandaDB", node2.head.mutable().props.get("name").get.asObject())
+ undo.undo()
+ transaction.close()
+ Assert.assertEquals(0, solrNodeStore.getRecorderSize)
+ solrNodeStore.clearAll()
+ Assert.assertEquals(0, solrNodeStore.getRecorderSize)
+ }
+}
diff --git a/itest/src/test/scala/external-properties/PredicateTest.scala b/itest/src/test/scala/external-properties/PredicateTest.scala
new file mode 100644
index 00000000..1b767eaa
--- /dev/null
+++ b/itest/src/test/scala/external-properties/PredicateTest.scala
@@ -0,0 +1,147 @@
+package Externalproperties
+
+import java.io.{File, FileInputStream}
+import java.util.Properties
+
+import cn.pandadb.externalprops.{InMemoryPropertyNodeStore, InSolrPropertyNodeStore, MutableNodeWithProperties, NodeWithProperties}
+import org.junit.{Assert, Test}
+import org.neo4j.cypher.internal.runtime.interpreted.{NFLessThan, NFPredicate, _}
+import org.neo4j.driver.{AuthTokens, GraphDatabase, Transaction, TransactionWork}
+import org.neo4j.values.{AnyValue, AnyValues}
+import org.neo4j.values.storable.Values
+
+import scala.collection.mutable.ArrayBuffer
+
+/**
+ * Created by codeBabyLin on 2019/12/6.
+ */
+
+class PredicateTest {
+
+ val configFile = new File("./testdata/neo4j.conf")
+ val props = new Properties()
+ props.load(new FileInputStream(configFile))
+ val zkString = props.getProperty("external.properties.store.solr.zk")
+ val collectionName = props.getProperty("external.properties.store.solr.collection")
+
+ def prepareData(solrNodeStore: InSolrPropertyNodeStore): Int = {
+
+ val node1 = MutableNodeWithProperties(1)
+ node1.labels += "database"
+ node1.props += "name" -> Values.of("pandaDB")
+ node1.props += "age" -> Values.of(1)
+ node1.props += "nation" -> Values.of("China")
+
+ val node2 = MutableNodeWithProperties(2)
+ node2.labels += "database"
+ node2.props += "name" -> Values.of("neo4j")
+ node2.props += "age" -> Values.of(5)
+
+ val node3 = MutableNodeWithProperties(3)
+ node3.labels += "person"
+ node3.props += "name" -> Values.of("bluejoe")
+ node3.props += "age" -> Values.of(40)
+
+ val node4 = MutableNodeWithProperties(4)
+ node4.labels += "person"
+ node4.props += "name" -> Values.of("jason")
+ node4.props += "age" -> Values.of(39)
+
+ val node5 = MutableNodeWithProperties(5)
+ node5.labels += "person"
+ node5.props += "name" -> Values.of("Airzihao")
+ node5.props += "age" -> Values.of(18)
+
+ val nodeArray = ArrayBuffer[NodeWithProperties]()
+ nodeArray += NodeWithProperties(node1.id, node1.props.toMap, node1.labels)
+ nodeArray += NodeWithProperties(node2.id, node2.props.toMap, node2.labels)
+ nodeArray += NodeWithProperties(node3.id, node3.props.toMap, node3.labels)
+ nodeArray += NodeWithProperties(node4.id, node4.props.toMap, node4.labels)
+ nodeArray += NodeWithProperties(node5.id, node5.props.toMap, node5.labels)
+ solrNodeStore.addNodes(nodeArray)
+ solrNodeStore.getRecorderSize
+ }
+
+ @Test
+ def test3() {
+ val solrNodeStore = new InSolrPropertyNodeStore(zkString, collectionName)
+ solrNodeStore.clearAll()
+ Assert.assertEquals(0, solrNodeStore.getRecorderSize)
+
+ Assert.assertEquals(5, prepareData(solrNodeStore))
+
+ val nodeList1 = solrNodeStore.getNodesByLabel("person")
+ val nodeList2 = solrNodeStore.getNodesByLabel("database")
+
+ Assert.assertEquals(3, nodeList1.size)
+ Assert.assertEquals(2, nodeList2.size)
+
+ var res1 = solrNodeStore.filterNodesWithProperties(NFGreaterThan("age", Values.of(39)))
+ Assert.assertEquals(1, res1.size)
+
+ res1 = solrNodeStore.filterNodesWithProperties(NFGreaterThanOrEqual("age", Values.of(39)))
+ Assert.assertEquals(2, res1.size)
+
+ res1 = solrNodeStore.filterNodesWithProperties(NFLessThan("age", Values.of(18)))
+ Assert.assertEquals(2, res1.size)
+
+ res1 = solrNodeStore.filterNodesWithProperties(NFLessThanOrEqual("age", Values.of(18)))
+ Assert.assertEquals(3, res1.size)
+
+ res1 = solrNodeStore.filterNodesWithProperties(NFEquals("age", Values.of(18)))
+ Assert.assertEquals("Airzihao", res1.head.mutable().props.get("name").get.asObject())
+
+ res1 = solrNodeStore.filterNodesWithProperties(NFContainsWith("name", "joe"))
+ Assert.assertEquals(1, res1.size)
+ Assert.assertEquals("bluejoe", res1.head.mutable().props.get("name").get.asObject())
+
+ res1 = solrNodeStore.filterNodesWithProperties(NFEndsWith("name", "son"))
+ Assert.assertEquals(1, res1.size)
+ Assert.assertEquals(39.toLong, res1.head.mutable().props.get("age").get.asObject())
+
+ res1 = solrNodeStore.filterNodesWithProperties(NFStartsWith("name", "pan"))
+ Assert.assertEquals(1, res1.size)
+ Assert.assertEquals("database", res1.head.labels.head)
+
+ res1 = solrNodeStore.filterNodesWithProperties(NFStartsWith("name", "pan"))
+ Assert.assertEquals(1, res1.size)
+ Assert.assertEquals("database", res1.head.labels.head)
+
+ res1 = solrNodeStore.filterNodesWithProperties(NFFalse())
+ Assert.assertEquals(0, res1.size)
+
+ res1 = solrNodeStore.filterNodesWithProperties(NFTrue())
+ Assert.assertEquals(5, res1.size)
+
+ res1 = solrNodeStore.filterNodesWithProperties(NFHasProperty("nation"))
+ Assert.assertEquals(1, res1.size)
+ Assert.assertEquals("pandaDB", res1.head.props.get("name").get.asObject())
+
+ res1 = solrNodeStore.filterNodesWithProperties(NFIsNull("nation"))
+ Assert.assertEquals(4, res1.size)
+
+ res1 = solrNodeStore.filterNodesWithProperties(NFNotNull("nation"))
+ Assert.assertEquals(1, res1.size)
+ Assert.assertEquals("China", res1.head.props.get("nation").get.asObject())
+
+ res1 = solrNodeStore.filterNodesWithProperties(NFNotEquals("age", Values.of(18)))
+ Assert.assertEquals(4, res1.size)
+
+ res1 = solrNodeStore.filterNodesWithProperties(NFRegexp("name", ".?lue.*"))
+ Assert.assertEquals(40, res1.head.mutable().props.get("age").get.asObject().toString.toLong)
+
+ res1 = solrNodeStore.filterNodesWithProperties(NFAnd(NFIsNull("nation"), NFLessThanOrEqual("age", Values.of(18))))
+ Assert.assertEquals(2, res1.size)
+
+ res1 = solrNodeStore.filterNodesWithProperties(NFNot(NFIsNull("nation")))
+ Assert.assertEquals(1, res1.size)
+ Assert.assertEquals("China", res1.head.props.get("nation").get.asObject())
+
+ res1 = solrNodeStore.filterNodesWithProperties(NFOr(NFNotNull("nation"), NFGreaterThanOrEqual("age", Values.of(40))))
+ Assert.assertEquals(2, res1.size)
+
+ solrNodeStore.clearAll()
+ Assert.assertEquals(0, solrNodeStore.getRecorderSize)
+ }
+
+}
diff --git a/itest/src/test/scala/external-properties/SolrIterableTest.scala b/itest/src/test/scala/external-properties/SolrIterableTest.scala
new file mode 100644
index 00000000..19f741c8
--- /dev/null
+++ b/itest/src/test/scala/external-properties/SolrIterableTest.scala
@@ -0,0 +1,87 @@
+import java.io.{File, FileInputStream}
+import java.util.Properties
+
+import cn.pandadb.externalprops.{InSolrPropertyNodeStore, MutableNodeWithProperties, NodeWithProperties, SolrQueryResults}
+import org.apache.solr.client.solrj.SolrQuery
+import org.junit.{Assert, Test}
+import org.neo4j.values.storable.Values
+
+import scala.collection.mutable.ArrayBuffer
+
+class SolrIterableTest {
+
+ val configFile = new File("./testdata/neo4j.conf")
+ val props = new Properties()
+ props.load(new FileInputStream(configFile))
+ val zkString = props.getProperty("external.properties.store.solr.zk")
+ val collectionName = props.getProperty("external.properties.store.solr.collection")
+
+ //val collectionName = "test"
+
+ //test for node label add and remove
+ def prepareData(solrNodeStore: InSolrPropertyNodeStore): Int = {
+
+ val node1 = MutableNodeWithProperties(1)
+ node1.labels += "database"
+ node1.props += "name" -> Values.of("pandaDB")
+ node1.props += "age" -> Values.of(1)
+ node1.props += "nation" -> Values.of("China")
+
+ val node2 = MutableNodeWithProperties(2)
+ node2.labels += "database"
+ node2.props += "name" -> Values.of("neo4j")
+ node2.props += "age" -> Values.of(5)
+
+ val node3 = MutableNodeWithProperties(3)
+ node3.labels += "person"
+ node3.props += "name" -> Values.of("bluejoe")
+ node3.props += "age" -> Values.of(40)
+
+ val node4 = MutableNodeWithProperties(4)
+ node4.labels += "person"
+ node4.props += "name" -> Values.of("jason")
+ node4.props += "age" -> Values.of(39)
+
+ val node5 = MutableNodeWithProperties(5)
+ node5.labels += "person"
+ node5.props += "name" -> Values.of("Airzihao")
+ node5.props += "age" -> Values.of(18)
+
+ val nodeArray = ArrayBuffer[NodeWithProperties]()
+ nodeArray += NodeWithProperties(node1.id, node1.props.toMap, node1.labels)
+ nodeArray += NodeWithProperties(node2.id, node2.props.toMap, node2.labels)
+ nodeArray += NodeWithProperties(node3.id, node3.props.toMap, node3.labels)
+ nodeArray += NodeWithProperties(node4.id, node4.props.toMap, node4.labels)
+ nodeArray += NodeWithProperties(node5.id, node5.props.toMap, node5.labels)
+ nodeArray += NodeWithProperties(node1.id + 5, node1.props.toMap, node1.labels)
+ nodeArray += NodeWithProperties(node2.id + 5, node2.props.toMap, node2.labels)
+ nodeArray += NodeWithProperties(node3.id + 5, node3.props.toMap, node3.labels)
+ nodeArray += NodeWithProperties(node4.id + 5, node4.props.toMap, node4.labels)
+ nodeArray += NodeWithProperties(node5.id + 5, node5.props.toMap, node5.labels)
+ nodeArray += NodeWithProperties(node1.id + 10, node1.props.toMap, node1.labels)
+ nodeArray += NodeWithProperties(node2.id + 10, node2.props.toMap, node2.labels)
+ nodeArray += NodeWithProperties(node3.id + 10, node3.props.toMap, node3.labels)
+ nodeArray += NodeWithProperties(node4.id + 10, node4.props.toMap, node4.labels)
+ nodeArray += NodeWithProperties(node5.id + 10, node5.props.toMap, node5.labels)
+ solrNodeStore.addNodes(nodeArray)
+ solrNodeStore.getRecorderSize
+ }
+ //scalastyle:off
+ @Test
+ def test1() {
+ val solrNodeStore = new InSolrPropertyNodeStore(zkString, collectionName)
+ solrNodeStore.clearAll()
+ Assert.assertEquals(0, solrNodeStore.getRecorderSize)
+ Assert.assertEquals(15, prepareData(solrNodeStore))
+ val query = new SolrQuery("*:*")
+ val res = new SolrQueryResults(solrNodeStore._solrClient, query, 10)
+ val it = res.iterator2().toIterable
+ it.foreach(u => println(u))
+ /* while (it.readNextPage()) {
+ it.getCurrentData().foreach(u => println(u))
+ }*/
+ // res.getAllResults().foreach(u => println(u))
+
+ }
+
+}
diff --git a/itest/src/test/scala/external-properties/api-query/CreateQueryTest.scala b/itest/src/test/scala/external-properties/api-query/CreateQueryTest.scala
new file mode 100644
index 00000000..00463ec8
--- /dev/null
+++ b/itest/src/test/scala/external-properties/api-query/CreateQueryTest.scala
@@ -0,0 +1,137 @@
+
+import java.io.File
+import java.time.ZoneId
+import scala.collection.JavaConverters._
+import cn.pandadb.server.PNodeServer
+import org.junit.{After, Assert, Before, Test}
+import org.neo4j.graphdb.factory.GraphDatabaseFactory
+import org.neo4j.graphdb.{GraphDatabaseService, Result, Label}
+import org.neo4j.io.fs.FileUtils
+import cn.pandadb.externalprops.{InMemoryPropertyNodeStore, InMemoryPropertyNodeStoreFactory}
+import org.neo4j.values.storable.{DateTimeValue, DateValue, LocalDateTimeValue, TimeValue}
+
+
+class CreateNodeQueryAPITest extends CreateQueryTestBase {
+ val tmpns = InMemoryPropertyNodeStore
+
+ @Test
+ def test1(): Unit = {
+ // create one node
+ val tx1 = db.beginTx()
+ val label1 = Label.label("Person")
+ val node1 = db.createNode(label1)
+
+ // before tx close, data haven't flush to store
+ assert(tmpns.nodes.size == 0)
+ tx1.success()
+ tx1.close()
+ // after tx close, data flushed to store
+ val id1 = node1.getId
+ assert(id1 != -1 )
+ assert(tmpns.nodes.size == 1)
+ assert(tmpns.nodes.get(id1).get.props.size == 0)
+ assert(tmpns.nodes.get(id1).get.labels.size == 1 && tmpns.nodes.get(id1).get.labels.toList(0) == "Person")
+
+ val tx2 = db.beginTx()
+ val node2 = db.createNode()
+ // before tx close, data haven't flush to store
+ assert(tmpns.nodes.size == 1)
+ tx2.success()
+ tx2.close()
+
+ // after tx close, data flushed to store
+ val id2 = node2.getId
+ assert(id2 != -1)
+ assert(tmpns.nodes.size == 2)
+ assert(tmpns.nodes.get(id2).get.props.size == 0)
+ assert(tmpns.nodes.get(id2).get.labels == null || tmpns.nodes.get(id2).get.labels.size == 0)
+ }
+
+ @Test
+ def test2(): Unit = {
+ // create node with labels and properties
+ val tx = db.beginTx()
+ val label1 = Label.label("Person")
+ val label2 = Label.label("Man")
+ val node1 = db.createNode(label1)
+ node1.setProperty("name", "test01")
+ node1.setProperty("age", 10)
+ node1.setProperty("adult", false)
+ val node2 = db.createNode(label1, label2)
+ node2.setProperty("name", "test02")
+ node2.setProperty("age", 20)
+ node2.setProperty("adult", true)
+
+ // before tx close, data haven't flush to store
+ assert(tmpns.nodes.size == 0)
+ tx.success();
+ tx.close()
+
+ var id1: Long = node1.getId
+ var id2: Long = node2.getId
+
+ // after tx close, data flushed to store
+ assert(tmpns.nodes.get(id1).size == 1 && tmpns.nodes.get(id2).size == 1)
+
+ val fields1 = tmpns.nodes.get(id1).get.props
+ assert(fields1.size == 3 && fields1("name").equals("test01") && fields1("age").equals(10) && fields1("adult").equals(false) )
+ val labels1 = tmpns.nodes.get(id1).get.labels.toList
+ assert(labels1.size == 1 && labels1(0) == "Person")
+
+ val fields2 = tmpns.nodes.get(id2).get.props
+ assert(fields2.size == 3 && fields2("name").equals("test02") && fields2("age").equals(20) && fields2("adult").equals(true) )
+ val labels2 = tmpns.nodes.get(id2).get.labels.toList
+ assert(labels2.size == 2 && labels2.contains("Person") && labels2.contains("Man") )
+
+ }
+
+ @Test
+ def test3(): Unit = {
+ // create node with DateTime type property and array value
+ val tx = db.beginTx()
+ val label1 = Label.label("Person")
+ val node1 = db.createNode(label1)
+ node1.setProperty("name", "test01")
+ node1.setProperty("age", 10)
+ node1.setProperty("adult", false)
+
+ val born1 = DateValue.date(2019, 1, 1)
+ val born2 = TimeValue.time(12, 5, 1, 0, "Z")
+ val born3 = DateTimeValue.datetime(2019, 1, 2,
+ 12, 5, 15, 0, "Australia/Eucla")
+ val born4 = DateTimeValue.datetime(2015, 6, 24,
+ 12, 50, 35, 556, ZoneId.of("Z"))
+ node1.setProperty("born1", born1)
+ node1.setProperty("born2", born2)
+ node1.setProperty("born3", born3)
+ node1.setProperty("born4", born4)
+
+ val arr1 = Array(1, 2, 3)
+ val arr2 = Array("aa", "bb", "cc")
+ val arr3 = Array(true, false)
+ node1.setProperty("arr1", arr1)
+ node1.setProperty("arr2", arr2)
+ node1.setProperty("arr3", arr3)
+
+ assert(tmpns.nodes.size == 0)
+ tx.success();
+ tx.close()
+
+ var id1: Long = node1.getId
+ assert(tmpns.nodes.size == 1)
+ assert(tmpns.nodes.get(id1).size == 1 )
+
+ val fields1 = tmpns.nodes.get(id1).get.props
+ assert(fields1.size == 10 )
+ assert(fields1("born1").asInstanceOf[DateValue].equals(born1))
+ assert(fields1("born2").asInstanceOf[TimeValue].equals(born2))
+ assert(fields1("born3").equals(born3))
+ assert(fields1("born4").equals(born4))
+ assert(fields1("arr1").equals(arr1))
+ assert(fields1("arr2").equals(arr2))
+ assert(fields1("arr3").equals(arr3))
+
+ }
+
+
+}
diff --git a/itest/src/test/scala/external-properties/api-query/MatchQueryTest.scala b/itest/src/test/scala/external-properties/api-query/MatchQueryTest.scala
new file mode 100644
index 00000000..b2375b7a
--- /dev/null
+++ b/itest/src/test/scala/external-properties/api-query/MatchQueryTest.scala
@@ -0,0 +1,64 @@
+
+import org.junit.Test
+import org.neo4j.graphdb.Label
+
+import scala.collection.JavaConverters._
+
+class MatchQueryAPITest extends MatchQueryTestBase {
+
+ @Test
+ def test1(): Unit = {
+ // initData
+ val tx = db.beginTx()
+ val label1 = Label.label("Person")
+ val label2 = Label.label("Man")
+ val node1 = db.createNode(label1)
+ node1.setProperty("name", "test01")
+ node1.setProperty("age", 10)
+ node1.setProperty("adult", false)
+ val node2 = db.createNode(label1, label2)
+ node2.setProperty("name", "test02")
+ node2.setProperty("age", 20)
+ node2.setProperty("adult", true)
+ tx.success()
+ tx.close()
+
+ // test getAllNodes()
+ val tx1 = db.beginTx()
+ val nodes1 = db.getAllNodes().iterator()
+ var count = 0
+ while (nodes1.hasNext) {
+ count += 1
+ nodes1.next()
+ }
+ tx1.close()
+ assert(2 == count)
+
+ // test getLabels()
+ val tx2 = db.beginTx()
+ val n1 = db.getNodeById(node1.getId)
+ val labels1 = n1.getLabels().asScala
+ for (label: Label <- labels1) {
+ assert(label.name() == "Person")
+ }
+ tx2.close()
+
+ // test getAllProperties()
+ val tx3 = db.beginTx()
+ val n2 = db.getNodeById(node1.getId)
+ val props2 = n2.getAllProperties()
+ assert(props2.get("name").equals("test01"))
+ assert(props2.get("age").equals(10))
+ assert(props2.get("adult").equals(false))
+ tx3.close()
+
+ // test getProperty()
+ val tx4 = db.beginTx()
+ val n4 = db.getNodeById(node2.getId)
+ assert(n4.getProperty("name").equals("test02"))
+ assert(n4.getProperty("age").equals(20))
+ assert(n4.getProperty("adult").equals(true))
+ tx4.close()
+ }
+
+}
diff --git a/itest/src/test/scala/external-properties/api-query/UpdateQueryTest.scala b/itest/src/test/scala/external-properties/api-query/UpdateQueryTest.scala
new file mode 100644
index 00000000..9b37ebef
--- /dev/null
+++ b/itest/src/test/scala/external-properties/api-query/UpdateQueryTest.scala
@@ -0,0 +1,183 @@
+
+import java.io.File
+
+import scala.collection.JavaConverters._
+import cn.pandadb.server.PNodeServer
+import org.junit.{After, Before, Test}
+import org.neo4j.graphdb.factory.GraphDatabaseFactory
+import org.neo4j.graphdb.{GraphDatabaseService, Label}
+import org.neo4j.io.fs.FileUtils
+import cn.pandadb.externalprops.{InMemoryPropertyNodeStore, InMemoryPropertyNodeStoreFactory}
+
+
+class UpdatePropertyQueryAPITest extends UpdateQueryTestBase {
+ val tmpns = InMemoryPropertyNodeStore
+
+ @Test
+ def test1(): Unit = {
+ // update and add node properties
+
+ // create node
+ val tx = db.beginTx()
+ val label1 = Label.label("Person")
+ val node1 = db.createNode(label1)
+ // before tx close, data haven't flush to store
+ assert(tmpns.nodes.size == 0)
+ tx.success()
+ tx.close()
+ // after tx close, data flushed to store
+ val id1 = node1.getId
+ assert(id1 != -1 )
+ assert(tmpns.nodes.size == 1)
+ assert(tmpns.nodes.get(id1).get.props.size == 0)
+ assert(tmpns.nodes.get(id1).get.labels.size == 1 && tmpns.nodes.get(id1).get.labels.toList(0) == "Person")
+
+ // add properties
+
+ val tx2 = db.beginTx()
+ node1.setProperty("name", "test01")
+ node1.setProperty("age", 10)
+ node1.setProperty("sex", "male")
+ // before tx close, data haven't flush to store
+ assert(tmpns.nodes.size == 1)
+ assert(tmpns.nodes.get(id1).get.props.size == 0)
+ tx2.success()
+ tx2.close()
+ // after tx close, data flushed to store
+ assert(tmpns.nodes.size == 1)
+ val fields = tmpns.nodes.get(id1).get.props
+ assert(fields.size == 3 && fields("name").equals("test01") && fields("age").equals(10) &&
+ fields("sex").equals("male"))
+
+ // update properties
+ val tx3 = db.beginTx()
+ node1.setProperty("name", "test02")
+ node1.setProperty("age", 20)
+ // before tx close, data haven't flush to store
+ val fields2 = tmpns.nodes.get(id1).get.props
+ assert(fields2.size == 3 && fields2("name").equals("test01") && fields2("age").equals(10) &&
+ fields2("sex").equals("male"))
+ tx3.success()
+ tx3.close()
+ // after tx close, data flushed to store
+ assert(tmpns.nodes.size == 1)
+ val fields3 = tmpns.nodes.get(id1).get.props
+ assert(fields3.size == 3 && fields3("name").equals("test02") && fields3("age").equals(20) &&
+ fields3("sex").equals("male"))
+ }
+
+ @Test
+ def test2(): Unit = {
+ // delete node properties
+
+ // create node
+ val tx1 = db.beginTx()
+ val label1 = Label.label("Person")
+ val node1 = db.createNode(label1)
+ node1.setProperty("name", "test01")
+ node1.setProperty("age", 10)
+ node1.setProperty("sex", "male")
+ val id1 = node1.getId
+ tx1.success()
+ tx1.close()
+ // after tx close, data flushed to store
+ assert(tmpns.nodes.size == 1)
+ val fields = tmpns.nodes.get(id1).get.props
+ assert(fields.size == 3 && fields("name").equals("test01") && fields("age").equals(10) &&
+ fields("sex").equals("male"))
+
+ // delete properties
+ val tx2 = db.beginTx()
+ node1.removeProperty("name")
+ node1.removeProperty("age")
+ // before tx close, data haven't flush to store
+ assert(tmpns.nodes.size == 1)
+ val fields1 = tmpns.nodes.get(id1).get.props
+ assert(fields1.size == 3 && fields1("name").equals("test01") && fields1("age").equals(10) &&
+ fields1("sex").equals("male"))
+ tx2.success()
+ tx2.close()
+ // after tx close, data flushed to store
+ assert(tmpns.nodes.size == 1)
+ val fields2 = tmpns.nodes.get(id1).get.props
+ assert(fields2.size == 1 && fields2("sex").equals("male"))
+
+ }
+
+}
+
+
+class UpdateLabelQueryAPITest extends UpdateQueryTestBase {
+ val tmpns = InMemoryPropertyNodeStore
+
+
+ @Test
+ def test1(): Unit = {
+ // add labels
+
+ // create node
+ val tx = db.beginTx()
+ val label1 = Label.label("Person")
+ val node1 = db.createNode(label1)
+ // before tx close, data haven't flush to store
+ assert(tmpns.nodes.size == 0)
+ tx.success()
+ tx.close()
+ // after tx close, data flushed to store
+ val id1 = node1.getId
+ assert(tmpns.nodes.size == 1)
+ assert(tmpns.nodes.get(id1).get.labels.size == 1 && tmpns.nodes.get(id1).get.labels.toList(0) == "Person")
+
+ // add label
+ val tx1 = db.beginTx()
+ val label2 = Label.label("Man")
+ node1.addLabel(label2)
+ // before tx close, data haven't flush to store
+ assert(tmpns.nodes.size == 1)
+ assert(tmpns.nodes.get(id1).get.labels.size == 1 )
+ tx1.success()
+ tx1.close()
+
+ // after tx close, data flushed to store
+ assert(tmpns.nodes.size == 1)
+ val labels = tmpns.nodes.get(id1).get.labels.toList
+ assert(labels.size == 2 && labels.contains("Person") && labels.contains("Man") )
+ }
+
+ @Test
+ def test2(): Unit = {
+ // remove one label
+ // create node
+ val tx = db.beginTx()
+ val label1 = Label.label("Person")
+ val label2 = Label.label("Boy")
+ val label3 = Label.label("Man")
+ val node1 = db.createNode(label1, label2, label3)
+ // before tx close, data haven't flush to store
+ assert(tmpns.nodes.size == 0)
+ tx.success()
+ tx.close()
+ // after tx close, data flushed to store
+ val id1 = node1.getId
+ assert(tmpns.nodes.size == 1)
+ val labels = tmpns.nodes.get(id1).get.labels.toList
+ assert(labels.size == 3 && labels.contains("Person") && labels.contains("Man") && labels.contains("Boy"))
+
+ // add label
+ val tx1 = db.beginTx()
+ node1.removeLabel(label2)
+ // before tx close, data haven't flush to store
+ assert(tmpns.nodes.size == 1)
+ assert(tmpns.nodes.get(id1).get.labels.size == 3 )
+ val labels2 = tmpns.nodes.get(id1).get.labels.toList
+ assert(labels2.size == 3 && labels2.contains("Person") && labels2.contains("Man") && labels2.contains("Boy"))
+ tx1.success()
+ tx1.close()
+
+ // after tx close, data flushed to store
+ assert(tmpns.nodes.size == 1)
+ val labels3 = tmpns.nodes.get(id1).get.labels.toList
+ assert(labels3.size == 2 && labels3.contains("Person") && labels3.contains("Man") )
+ }
+
+}
\ No newline at end of file
diff --git a/itest/src/test/scala/external-properties/cyhper-query/CreateQueryTest.scala b/itest/src/test/scala/external-properties/cyhper-query/CreateQueryTest.scala
new file mode 100644
index 00000000..5be1637d
--- /dev/null
+++ b/itest/src/test/scala/external-properties/cyhper-query/CreateQueryTest.scala
@@ -0,0 +1,273 @@
+
+import java.io.File
+import java.time.ZoneId
+
+import scala.collection.JavaConverters._
+import cn.pandadb.server.PNodeServer
+import org.junit.{After, Assert, Before, Test}
+import org.neo4j.graphdb.factory.GraphDatabaseFactory
+import org.neo4j.graphdb.{GraphDatabaseService, Result}
+import org.neo4j.io.fs.FileUtils
+import cn.pandadb.externalprops.{CustomPropertyNodeStore, InMemoryPropertyNodeStore, InMemoryPropertyNodeStoreFactory}
+import org.neo4j.values.storable.{DateTimeValue, DateValue, LocalDateTimeValue, TimeValue}
+
+trait CreateQueryTestBase extends QueryTestBase {
+
+}
+
+class CreateNodeQueryTest extends CreateQueryTestBase {
+ val tmpns = InMemoryPropertyNodeStore
+
+ @Test
+ def test1(): Unit = {
+ // create one node
+ val query = "create (n1:Person) return id(n1)"
+ val rs = db.execute(query)
+ var id1: Long = -1
+ if (rs.hasNext) {
+ val row = rs.next()
+ id1 = row.get("id(n1)").toString.toLong
+ }
+ assert(id1 != -1 )
+ assert(tmpns.nodes.size == 1)
+ assert(tmpns.nodes.get(id1).get.props.size == 0)
+ assert(tmpns.nodes.get(id1).get.labels.size == 1 && tmpns.nodes.get(id1).get.labels.toList(0) == "Person")
+
+ val query2 = "create (n1) return id(n1)"
+ val rs2 = db.execute(query2)
+ var id2: Long = -1
+ if (rs2.hasNext) {
+ val row = rs2.next()
+ id2 = row.get("id(n1)").toString.toLong
+ }
+ assert(id2 != -1)
+ assert(tmpns.nodes.size == 2)
+ assert(tmpns.nodes.get(id2).get.props.size == 0)
+ assert(tmpns.nodes.get(id2).get.labels.size == 0)
+ }
+
+ @Test
+ def test2(): Unit = {
+ // create multiple nodes
+ val tx = db.beginTx()
+ val query = "create (n1:Person),(n2:Man) return id(n1),id(n2)"
+ val rs = db.execute(query)
+ var id1: Long = 0
+ var id2: Long = 0
+ if (rs.hasNext) {
+ val row = rs.next()
+ id1 = row.get("id(n1)").toString.toLong
+ id2 = row.get("id(n2)").toString.toLong
+ }
+ assert(id1 != -1 && id2 != -1)
+
+ // before tx close, data haven't flush to store
+ assert(tmpns.nodes.size == 0)
+ tx.success()
+ tx.close()
+
+ // after tx close, data flushed to store
+ assert(tmpns.nodes.size == 2)
+ assert(tmpns.nodes.get(id1).size == 1 && tmpns.nodes.get(id2).size == 1)
+ assert(tmpns.nodes.get(id1).get.props.size == 0 )
+ assert(tmpns.nodes.get(id1).get.labels.size == 1 && tmpns.nodes.get(id1).get.labels.toList(0) == "Person")
+ assert(tmpns.nodes.get(id2).get.props.size == 0 )
+ assert(tmpns.nodes.get(id2).get.labels.size == 1 && tmpns.nodes.get(id2).get.labels.toList(0) == "Man")
+ assert(tmpns.nodes.get(id2).get.labels.size == 1 && tmpns.nodes.get(id2).get.labels.toList(0) == "Man")
+ }
+
+ @Test
+ def test3(): Unit = {
+ // create node with labels and properties
+ val query =
+ """CREATE (n1:Person { name:'test01', age:10, adult:False})
+ |CREATE (n2:Person:Man { name:'test02', age:20, adult:True})
+ |RETURN id(n1),id(n2)
+ """.stripMargin
+ val rs = db.execute(query)
+ var id1: Long = -1
+ var id2: Long = -1
+ if (rs.hasNext) {
+ val row = rs.next()
+ id1 = row.get("id(n1)").toString.toLong
+ id2 = row.get("id(n2)").toString.toLong
+ }
+
+ // Results have been visited, tx closed and data haven flush to store
+ assert(tmpns.nodes.get(id1).size == 1 && tmpns.nodes.get(id2).size == 1)
+
+ val fields1 = tmpns.nodes.get(id1).get.props
+ assert(fields1.size == 3 && fields1("name").equals("test01") && fields1("age").equals(10) && fields1("adult").equals(false) )
+ val labels1 = tmpns.nodes.get(id1).get.labels.toList
+ assert(labels1.size == 1 && labels1(0) == "Person")
+
+ val fields2 = tmpns.nodes.get(id2).get.props
+ assert(fields2.size == 3 && fields2("name").equals("test02") && fields2("age").equals(20) && fields2("adult").equals(true) )
+ val labels2 = tmpns.nodes.get(id2).get.labels.toList
+ assert(labels2.size == 2 && labels2.contains("Person") && labels2.contains("Man") )
+
+ }
+
+ @Test
+ def test4(): Unit = {
+ // create node with relationship
+ val tx = db.beginTx()
+ val query =
+ """CREATE (n1:Person { name:'test01', age:10})-[:WorksAt]->(neo:Company{business:'Software'})
+ |<-[:Create{from:1987}]-(n2:Ceo { name:'test02', age:20})
+ |RETURN id(n1),id(n2), id(neo)
+ """.stripMargin
+ val rs = db.execute(query)
+ var id1: Long = -1
+ var id2: Long = -1
+ var idNeo: Long = -1
+ if (rs.hasNext) {
+ val row = rs.next()
+ id1 = row.get("id(n1)").toString.toLong
+ id2 = row.get("id(n2)").toString.toLong
+ idNeo = row.get("id(neo)").toString.toLong
+ }
+
+ // before tx close, data haven't flush to store
+ assert(tmpns.nodes.size == 0)
+ tx.success();
+ tx.close()
+
+ // after tx close, data flushed to store
+ assert(tmpns.nodes.size == 3)
+ assert(tmpns.nodes.get(id1).size == 1 && tmpns.nodes.get(id2).size == 1 && tmpns.nodes.get(idNeo).size == 1)
+
+ val fields1 = tmpns.nodes.get(id1).get.props
+ assert(fields1.size == 2 && fields1("name").equals("test01") && fields1("age").equals(10) )
+ val labels1 = tmpns.nodes.get(id1).get.labels.toList
+ assert(labels1.size == 1 && labels1(0) == "Person")
+
+ val fields2 = tmpns.nodes.get(id2).get.props
+ assert(fields2.size == 2 && fields2("name").equals("test02") && fields2("age").equals(20) )
+ val labels2 = tmpns.nodes.get(id2).get.labels.toList
+ assert(labels2.size == 1 && labels2.contains("Ceo") )
+
+ val fields3 = tmpns.nodes.get(idNeo).get.props
+ assert(fields3.size == 1 && fields3("business").equals("Software"))
+ val labels3 = tmpns.nodes.get(idNeo).get.labels.toList
+ assert(labels3.size == 1 && labels3.contains("Company"))
+
+ }
+
+ @Test
+ def test5(): Unit = {
+ // create node with DateTime type property value
+ val tx = db.beginTx()
+ val query =
+ """CREATE (n1:Person { name:'test01',born1:date('2019-01-01'), born2:time('12:05:01')
+ |,born3:datetime('2019-01-02T12:05:15[Australia/Eucla]'), born4:datetime('2015-06-24T12:50:35.000000556Z')})
+ |RETURN id(n1)
+ """.stripMargin
+ val rs = db.execute(query)
+ var id1: Long = -1
+ if (rs.hasNext) {
+ val row = rs.next()
+ id1 = row.get("id(n1)").toString.toLong
+ }
+ assert(tmpns.nodes.size == 0)
+ tx.success();
+ tx.close()
+
+ assert(tmpns.nodes.size == 1)
+ assert(tmpns.nodes.get(id1).size == 1 )
+
+ val fields1 = tmpns.nodes.get(id1).get.props
+ assert(fields1.size == 5 )
+ val born1 = DateValue.date(2019, 1, 1)
+ val born2 = TimeValue.time(12, 5, 1, 0, "Z")
+ val born3 = DateTimeValue.datetime(2019, 1, 2,
+ 12, 5, 15, 0, "Australia/Eucla")
+ val born4 = DateTimeValue.datetime(2015, 6, 24,
+ 12, 50, 35, 556, ZoneId.of("Z"))
+
+ assert(fields1("born1").asInstanceOf[DateValue].equals(born1))
+ assert(fields1("born2").asInstanceOf[TimeValue].equals(born2))
+ assert(fields1("born3").equals(born3))
+ assert(fields1("born4").equals(born4))
+ }
+
+ @Test
+ def test6(): Unit = {
+ // create node with Array type property value
+ val query =
+ """CREATE (n1:Person { name:'test01',titles:["ceo","ui","dev"],
+ |salaries:[10000,20000,30597,500954], boolattr:[False,True,false,true]})
+ |RETURN id(n1)
+ """.stripMargin
+ val rs = db.execute(query)
+ var id1: Long = -1
+ if (rs.hasNext) {
+ val row = rs.next ()
+ id1 = row.get("id(n1)").toString.toLong
+ }
+
+ assert(tmpns.nodes.size == 1)
+ assert(tmpns.nodes.get(id1).size == 1 )
+
+ val fields1 = tmpns.nodes.get(id1).get.props
+ assert(fields1.size == 4 )
+ val titles = Array("ceo", "ui", "dev")
+ val salaries = Array(10000, 20000, 30597, 500954)
+ val boolattr = Array(false, true, false, true)
+ assert(fields1("titles").equals(titles))
+ assert(fields1("salaries").equals(salaries))
+ assert(fields1("boolattr").equals(boolattr))
+
+ }
+
+}
+
+
+class CreateNodeQueryTest2 extends QueryTestBase{
+ nodeStore = ""
+
+ @Test
+ def test1(): Unit = {
+ // create node with labels and properties
+ val query =
+ """CREATE (n1:Person { name:'test01', age:10, adult:False})
+ |CREATE (n2:Person:Man { name:'test02', age:20, adult:True})
+ |RETURN id(n1),id(n2)
+ """.stripMargin
+ val tx1 = db.beginTx()
+ val rs = db.execute(query)
+ var id1: Long = -1
+ var id2: Long = -1
+ if (rs.hasNext) {
+ val row = rs.next()
+ id1 = row.get("id(n1)").toString.toLong
+ id2 = row.get("id(n2)").toString.toLong
+ }
+ tx1.success()
+ tx1.close()
+
+ val query2 = s"match (n1:Person) return n1.name;"
+ val tx2 = db.beginTx()
+ val rs2 = db.execute(query2)
+ while (rs2.hasNext) {
+ val n1 = rs2.next()
+ println(n1.get("n1.name") )
+ }
+ tx2.close()
+
+ val tx3 = db.beginTx()
+ val nodes = db.getAllNodes.iterator()
+ while (nodes.hasNext){
+ val n1 = nodes.next()
+ val labels = n1.getLabels.asScala
+ var labelsStr= ""
+ for (lbl <- labels) {
+ labelsStr += "," + lbl.name
+ }
+ println(labelsStr)
+ }
+ tx3.close()
+
+ }
+}
+
diff --git a/itest/src/test/scala/external-properties/cyhper-query/MatchQueryTest.scala b/itest/src/test/scala/external-properties/cyhper-query/MatchQueryTest.scala
new file mode 100644
index 00000000..32f8fd4b
--- /dev/null
+++ b/itest/src/test/scala/external-properties/cyhper-query/MatchQueryTest.scala
@@ -0,0 +1,105 @@
+
+import org.junit.Test
+import org.neo4j.graphdb.Result
+
+
+trait MatchQueryTestBase extends QueryTestBase {
+ def doCreate(queryStr: String): Unit = {
+ val tx = db.beginTx()
+ val rs = db.execute(queryStr)
+ tx.success()
+ tx.close()
+ }
+
+ def initData(): Unit = {
+ val queryStr =
+ """
+ |CREATE (n1:Person:Student{name: 'test01',age:15, sex:'male', school: 'No1 Middle School'}),
+ |(n2:Person:Teacher{name: 'test02', age: 30, sex:'male', school: 'No1 Middle School', class: 'math'}),
+ |(n3:Person:Teacher{name: 'test03', age: 40, sex:'female', school: 'No1 Middle School', class: 'chemistry'})
+ | """.stripMargin
+ doCreate(queryStr)
+ }
+
+ def rsRowCount(rs: Result): Int = {
+ var count: Int = 0;
+ while (rs.hasNext) {
+ count += 1
+ println(rs.next())
+ }
+ return count
+ }
+}
+
+class MatchQueryTest extends MatchQueryTestBase {
+
+ @Test
+ def test1(): Unit = {
+ // filter nodes by label
+
+ initData()
+ // Get all nodes
+ val query1 =
+ """match (n) return n
+ | """.stripMargin
+ val rs = db.execute(query1)
+ assert(rsRowCount(rs) == 3)
+
+ // filter by label
+ val query2 = "match (n:Person) return n"
+ val rs2 = db.execute(query2)
+ assert(rsRowCount(rs2) == 3)
+
+ val query3 = "match (n:Teacher) return n"
+ val rs3 = db.execute(query3)
+ assert(rsRowCount(rs3) == 2)
+
+ val query4 = "match (n:Person:Student) return n"
+ val rs4 = db.execute(query4)
+ assert(rsRowCount(rs4) == 1)
+
+ }
+
+ @Test
+ def test2(): Unit = {
+ // filter by property
+ initData()
+
+ // filter by {}
+ val query1 = "match (n:Person{name: 'test01'}) return n"
+ val rs1 = db.execute(query1)
+ assert(rsRowCount(rs1) == 1)
+
+ // filter by where
+ val query2 = "match (n) where n.name='test01' return n"
+ val rs2 = db.execute(query2)
+ assert(rsRowCount(rs2) == 1)
+
+ // filter by where
+ val query3 = "match (n:Teacher) where n.age<35 and n.sex='male' return n"
+ val rs3 = db.execute(query3)
+ assert(rsRowCount(rs3) == 1)
+ }
+
+ @Test
+ def test3(): Unit = {
+ // get property
+ initData()
+
+ // filter by {}
+ val query1 = "match (n:Person{name: 'test01'}) return n.sex, n.age, n.class, n.school"
+ val rs1 = db.execute(query1)
+ assert(rsRowCount(rs1) == 1)
+ while (rs1.hasNext) {
+ val row = rs1.next()
+ val sex = row.get("n.sex")
+ assert("male" == sex)
+ val age = row.get("n.age")
+ assert(15 == age)
+ val school = row.get("n.school")
+ assert("No1 Middle School" == school)
+ }
+ }
+
+
+}
diff --git a/itest/src/test/scala/external-properties/cyhper-query/UpdateQueryTest.scala b/itest/src/test/scala/external-properties/cyhper-query/UpdateQueryTest.scala
new file mode 100644
index 00000000..4f7b3e1d
--- /dev/null
+++ b/itest/src/test/scala/external-properties/cyhper-query/UpdateQueryTest.scala
@@ -0,0 +1,296 @@
+
+import cn.pandadb.externalprops.InMemoryPropertyNodeStore
+import org.junit.Test
+
+
+trait UpdateQueryTestBase extends QueryTestBase {
+
+}
+
+class UpdatePropertyQueryTest extends UpdateQueryTestBase {
+ val tmpns = InMemoryPropertyNodeStore
+
+ @Test
+ def test1(): Unit = {
+ // update and add node properties using 'set n.prop1=value1,n.prop2=value2'
+
+ // create node
+ val tx = db.beginTx()
+ val query = "create (n1:Person) return id(n1)"
+ val rs = db.execute(query)
+ var id1: Long = -1
+ if (rs.hasNext) {
+ val row = rs.next()
+ id1 = row.get("id(n1)").toString.toLong
+ }
+ tx.success()
+ tx.close()
+ assert(id1 != -1)
+ assert(tmpns.nodes.size == 1)
+ assert(tmpns.nodes.get(id1).get.props.size == 0)
+ assert(tmpns.nodes.get(id1).get.labels.size == 1 && tmpns.nodes.get(id1).get.labels.toList(0) == "Person")
+
+ // update and add properties
+ val tx2 = db.beginTx()
+ val query2 = s"match (n1:Person) where id(n1)=$id1 set n1.name='test01', n1.age=10 return n1.name,n1.age"
+ db.execute(query2)
+ tx2.success()
+ tx2.close()
+ assert(tmpns.nodes.size == 1)
+ val fields = tmpns.nodes.get(id1).get.props
+ assert(fields.size == 2 && fields("name").equals("test01") && fields("age").equals(10))
+ }
+
+
+ @Test
+ def test2(): Unit = {
+ // update node properties using 'set n={prop1:value1, prop2:value2}'
+
+ // create node
+ val tx = db.beginTx()
+ val query = "create (n1:Person{name:'test01',age:10}) return id(n1)"
+ val rs = db.execute(query)
+ var id1: Long = -1
+ if (rs.hasNext) {
+ val row = rs.next()
+ id1 = row.get("id(n1)").toString.toLong
+ }
+ tx.success()
+ tx.close()
+ assert(id1 != -1)
+ assert(tmpns.nodes.size == 1)
+ val fields1 = tmpns.nodes.get(id1).get.props
+ assert(fields1.size == 2 && fields1("name").equals("test01") && fields1("age").equals(10))
+
+ // update property
+ val tx2 = db.beginTx()
+ val query2 = s"match (n1:Person) where id(n1)=$id1 set n1={name:'test02', sex:'male'} return n1"
+ db.execute(query2)
+ tx2.success()
+ tx2.close()
+ assert(tmpns.nodes.size == 1)
+ val fields2 = tmpns.nodes.get(id1).get.props
+ assert(fields2.size == 2 && fields2("name").equals("test02") && fields2("sex").equals("male"))
+ }
+
+
+ @Test
+ def test3(): Unit = {
+ // update or add node properties using 'set n +={prop1:value1, prop2:value2}'
+
+ // create node
+ val tx = db.beginTx()
+ val query = "create (n1:Person{name:'test01',age:10}) return id(n1)"
+ val rs = db.execute(query)
+ var id1: Long = -1
+ if (rs.hasNext) {
+ val row = rs.next()
+ id1 = row.get("id(n1)").toString.toLong
+ }
+ tx.success()
+ tx.close()
+ assert(id1 != -1)
+ assert(tmpns.nodes.size == 1)
+ val fields1 = tmpns.nodes.get(id1).get.props
+ assert(fields1.size == 2 && fields1("name").equals("test01") && fields1("age").equals(10))
+
+ // update property
+ val tx2 = db.beginTx()
+ val query2 = s"match (n1:Person) where id(n1)=$id1 set n1 +={name:'test02',sex:'male', work:'dev'} return n1"
+ db.execute(query2)
+ tx2.success()
+ tx2.close()
+ assert(tmpns.nodes.size == 1)
+ val fields2 = tmpns.nodes.get(id1).get.props
+ assert(fields2.size == 4 && fields2("name").equals("test02") && fields2("age").equals(10) &&
+ fields2("sex").equals("male") && fields2("work").equals("dev"))
+ }
+
+ @Test
+ def test4(): Unit = {
+ // remove node properties using 'remove n.prop1'
+
+ // create node
+ val tx = db.beginTx()
+ val query = "create (n1:Person{name:'test01',age:10}) return id(n1)"
+ val rs = db.execute(query)
+ var id1: Long = -1
+ if (rs.hasNext) {
+ val row = rs.next()
+ id1 = row.get("id(n1)").toString.toLong
+ }
+ tx.success()
+ tx.close()
+ assert(id1 != -1)
+ assert(tmpns.nodes.size == 1)
+ val fields1 = tmpns.nodes.get(id1).get.props
+ assert(fields1.size == 2 && fields1("name").equals("test01") && fields1("age").equals(10))
+
+ // remove one property
+ val tx2 = db.beginTx()
+ val query2 = s"match (n1:Person) where id(n1)=$id1 remove n1.age"
+ db.execute(query2)
+ tx2.success()
+ tx2.close()
+ assert(tmpns.nodes.size == 1)
+ val fields2 = tmpns.nodes.get(id1).get.props
+ assert(fields2.size == 1 && fields2("name").equals("test01"))
+ }
+
+ @Test
+ def test5(): Unit = {
+ // remove node all properties using 'set n={}'
+
+ // create node
+ val tx = db.beginTx()
+ val query = "create (n1:Person{name:'test01',age:10}) return id(n1)"
+ val rs = db.execute(query)
+ var id1: Long = -1
+ if (rs.hasNext) {
+ val row = rs.next()
+ id1 = row.get("id(n1)").toString.toLong
+ }
+ tx.success()
+ tx.close()
+ assert(id1 != -1)
+ assert(tmpns.nodes.size == 1)
+ val fields1 = tmpns.nodes.get(id1).get.props
+ assert(fields1.size == 2 && fields1("name").equals("test01") && fields1("age").equals(10))
+
+ // remove property
+ val tx2 = db.beginTx()
+ val query2 = s"match (n1:Person) where id(n1)=$id1 set n1={} return n1"
+ db.execute(query2)
+ tx2.success()
+ tx2.close()
+ assert(tmpns.nodes.size == 1)
+ val fields2 = tmpns.nodes.get(id1).get.props
+ assert(fields2.size == 0)
+ }
+
+}
+
+
+class UpdateLabelQueryTest extends UpdateQueryTestBase {
+ val tmpns = InMemoryPropertyNodeStore
+
+ @Test
+ def test1(): Unit = {
+ // add labels
+
+ // create node
+ val query = "create (n1:Person{name:'xx'}) return id(n1)"
+ val rs = db.execute(query)
+ var id1: Long = -1
+ if (rs.hasNext) {
+ val row = rs.next()
+ id1 = row.get("id(n1)").toString.toLong
+ }
+
+ assert(id1 != -1)
+ assert(tmpns.nodes.size == 1)
+ assert(tmpns.nodes.get(id1).get.props.size == 1)
+ assert(tmpns.nodes.get(id1).get.labels.size == 1 && tmpns.nodes.get(id1).get.labels.toList(0) == "Person")
+
+ // add labels
+ val tx1 = db.beginTx()
+ val query3 = s"match (n1:Person) where id(n1)=$id1 set n1:Man:Boy:Person return labels(n1)"
+ db.execute(query3)
+
+ // before tx close, data haven't flush to store
+ assert(tmpns.nodes.size == 1)
+ assert(tmpns.nodes.get(id1).get.props.size == 1)
+ assert(tmpns.nodes.get(id1).get.labels.size == 1 && tmpns.nodes.get(id1).get.labels.toList(0) == "Person")
+ tx1.success()
+ tx1.close()
+
+ // after tx close, data flushed to store
+ assert(tmpns.nodes.size == 1)
+ val labels = tmpns.nodes.get(id1).get.labels.toList
+ assert(labels.size == 3 && labels.contains("Person") && labels.contains("Man") && labels.contains("Boy"))
+ }
+
+ @Test
+ def test2(): Unit = {
+ // remove one label
+
+ // create node
+ val tx = db.beginTx()
+ val query = "create (n1:Person:Man{name:'xx'}) return id(n1)"
+ val rs = db.execute(query)
+ var id1: Long = -1
+ if (rs.hasNext) {
+ val row = rs.next()
+ id1 = row.get("id(n1)").toString.toLong
+ }
+ tx.success()
+ tx.close()
+ assert(id1 != -1)
+ assert(tmpns.nodes.size == 1)
+ assert(tmpns.nodes.get(id1).get.props.size == 1)
+ var labels1 = tmpns.nodes.get(id1).get.labels.toList
+ assert(labels1.size == 2 && labels1.contains("Person") && labels1.contains("Man"))
+
+ // update labels
+ val tx2 = db.beginTx()
+ val query3 = s"match (n1:Person) where id(n1)=$id1 remove n1:Person return labels(n1)"
+ db.execute(query3)
+
+ // before tx close, data haven't flush to store
+ assert(tmpns.nodes.size == 1)
+ assert(tmpns.nodes.get(id1).get.props.size == 1)
+ labels1 = tmpns.nodes.get(id1).get.labels.toList
+ assert(labels1.size == 2 && labels1.contains("Person") && labels1.contains("Man"))
+ tx2.success()
+ tx2.close()
+
+ // after tx close, data flushed to store
+ assert(tmpns.nodes.size == 1)
+ val labels2 = tmpns.nodes.get(id1).get.labels.toList
+ assert(labels2.size == 1 && labels2.contains("Man"))
+ }
+
+
+ @Test
+ def test3(): Unit = {
+ // remove multi labels
+
+ // create node
+ // val tx = db.beginTx()
+ val query = "create (n1:Person:Man:Boy{name:'xx'}) return id(n1)"
+ val rs = db.execute(query)
+ var id1: Long = -1
+ if (rs.hasNext) {
+ val row = rs.next()
+ id1 = row.get("id(n1)").toString.toLong
+ }
+ // tx.success()
+ // tx.close()
+ // Results have been visited, tx closed and data haven flush to store
+ assert(id1 != -1)
+ assert(tmpns.nodes.size == 1)
+ assert(tmpns.nodes.get(id1).get.props.size == 1)
+ var labels1 = tmpns.nodes.get(id1).get.labels.toList
+ assert(labels1.size == 3 && labels1.contains("Person") && labels1.contains("Man") && labels1.contains("Boy"))
+
+ // update labels
+ val tx2 = db.beginTx()
+ val query3 = s"match (n1:Person) where id(n1)=$id1 remove n1:Person:Boy return labels(n1)"
+ db.execute(query3)
+
+ // before tx close, data haven't flush to store
+ assert(tmpns.nodes.size == 1)
+ assert(tmpns.nodes.get(id1).get.props.size == 1)
+ labels1 = tmpns.nodes.get(id1).get.labels.toList
+ assert(labels1.size == 3 && labels1.contains("Person") && labels1.contains("Man") && labels1.contains("Boy"))
+ tx2.success()
+ tx2.close()
+
+ // after tx close, data flushed to store
+ assert(tmpns.nodes.size == 1)
+ val labels2 = tmpns.nodes.get(id1).get.labels.toList
+ assert(labels2.size == 1 && labels2.contains("Man"))
+ }
+
+
+}
\ No newline at end of file
diff --git a/itest/src/test/scala/external-properties/neo4jANDsolrPerformanceTest.scala b/itest/src/test/scala/external-properties/neo4jANDsolrPerformanceTest.scala
new file mode 100644
index 00000000..11ebd524
--- /dev/null
+++ b/itest/src/test/scala/external-properties/neo4jANDsolrPerformanceTest.scala
@@ -0,0 +1,247 @@
+package externals
+
+import java.io.{File, FileInputStream}
+import java.util.Properties
+import java.util.function.Consumer
+
+import cn.pandadb.externalprops.{InSolrPropertyNodeStore, NodeWithProperties, SolrQueryResults, SolrUtil}
+import org.apache.solr.client.solrj.SolrQuery
+import org.apache.solr.client.solrj.impl.CloudSolrClient
+import org.junit.{Assert, Test}
+import org.neo4j.cypher.internal.runtime.interpreted.{NFAnd, NFEquals, NFGreaterThan, NFLessThan, NFPredicate}
+import org.neo4j.driver.{AuthTokens, GraphDatabase}
+import org.neo4j.values.storable.Values
+import org.scalatest.selenium.WebBrowser.Query
+
+import scala.collection.JavaConversions._
+import scala.collection.mutable.ArrayBuffer
+
+class neo4jANDsolrPerformanceTest {
+
+ val configFile = new File("./testdata/codeBabyTest.conf")
+ val props = new Properties()
+ props.load(new FileInputStream(configFile))
+ val zkString = props.getProperty("external.properties.store.solr.zk")
+ val collectionName = props.getProperty("external.properties.store.solr.collection")
+
+ val solrNodeStore = new InSolrPropertyNodeStore(zkString, collectionName)
+ val _solrClient = {
+ val client = new CloudSolrClient(zkString);
+ client.setZkClientTimeout(30000);
+ client.setZkConnectTimeout(50000);
+ client.setDefaultCollection(collectionName);
+ client
+ }
+
+ //val size = solrNodeStore.getRecorderSize
+ //Assert.assertEquals(13738580, size)
+ val uri = "bolt://10.0.82.220:7687"
+ val driver = GraphDatabase.driver(uri,
+ AuthTokens.basic("neo4j", "bigdata"))
+ //val res = driver.session().run("match (n) return count(n)")
+ //scalastyle:off
+ val session = driver.session()
+
+ def solrIteratorTime(exp: NFPredicate): Unit ={
+ val time1 = System.currentTimeMillis()
+ val ssize = solrNodeStore.filterNodesWithProperties(exp).size
+ val time2 = System.currentTimeMillis()
+ println(s"solr Iterator time :${time2-time1},result size:$ssize")
+ }
+
+ def neo4jTime(cypher: String): Unit ={
+ val time1 = System.currentTimeMillis()
+ val nsize = session.run(cypher).list().size()
+ val time2 = System.currentTimeMillis()
+ println(s"neo4j Iterator time :${time2-time1},result size:$nsize")
+ }
+
+ def solrArray(q: String): Unit ={
+ val nodeArray = ArrayBuffer[NodeWithProperties]()
+ val solrQuery = new SolrQuery()
+ solrQuery.set(q)
+ val time1 = System.currentTimeMillis()
+ val size = _solrClient.query(solrQuery).getResults.getNumFound
+ solrQuery.setRows(size.toInt)
+
+ val res = _solrClient.query(solrQuery).getResults
+ res.foreach(u => nodeArray += SolrUtil.solrDoc2nodeWithProperties(u))
+ val ssize = nodeArray.size
+ val time2 = System.currentTimeMillis()
+ println(s"solr Array time :${time2-time1},result size:$ssize")
+
+ }
+
+ def test(exp: NFPredicate = null, cypher: String = null, q: String = null): Unit ={
+
+ if(q!=null) {
+ solrArray(q)
+ }
+
+ if (cypher!=null) {
+ neo4jTime(cypher)
+ }
+
+ if (exp!=null) {
+ solrIteratorTime(exp)
+ }
+
+ }
+
+ def testFiveFilters(): Unit ={
+
+ val s71 = NFEquals("labels", Values.of("person"))
+ val s72 = NFGreaterThan("citations", Values.of(400))
+ val s73 = NFGreaterThan("citations5", Values.of(80))
+ val s74 = NFLessThan("citations", Values.of(450))
+ val s75 = NFLessThan("citations5", Values.of(100))
+ val s76 = NFEquals("nationality", Values.of("China"))
+ val s712 = NFAnd(s71, s72)
+ val s734 = NFAnd(s73, s74)
+ val s756 = NFAnd(s75, s76)
+ val s71234 = NFAnd(s712, s734)
+ val s7 = NFAnd(s71234, s756)
+ val n7 = "MATCH (n:person) where n.citations>400 and n.citations<450 and n.nationality='China' and n.citations5<100 and n.citations5>80 return n"
+
+ val s81 = NFEquals("labels", Values.of("organization"))
+ val s82 = NFEquals("country", Values.of("China"))
+ val s83 = NFGreaterThan("citations", Values.of(200000))
+ val s84 = NFGreaterThan("citations5", Values.of(140000))
+ val s85 = NFLessThan("citations", Values.of(500000))
+ val s812 = NFAnd(s81, s82)
+ val s834 = NFAnd(s83, s84)
+ val s81234 = NFAnd(s812, s834)
+ val s8 = NFAnd(s81234, s85)
+ val n8 = "MATCH (n:organization) where n.citations>200000 and n.citations<500000 and n.country='China' and n.citations5>140000 RETURN n"
+
+ solrIteratorTime(s7)
+ neo4jTime(n7)
+
+ solrIteratorTime(s8)
+ neo4jTime(n8)
+
+ }
+
+ def testThreeFilter(): Unit ={
+
+ val sq4501 = NFEquals("labels", Values.of("paper"))
+ val sq4502 = NFGreaterThan("citation", Values.of(350))
+ val sq4503 = NFEquals("country", Values.of("India"))
+ val s4q5012 = NFAnd(sq4501, sq4502)
+ val s4q50123 = NFAnd(s4q5012, sq4503)
+ val n4q50 = "MATCH (n:paper) where n.citation >350 and n.country='India' return n"
+
+ val sq5501 = NFEquals("labels", Values.of("person"))
+ val sq5502 = NFGreaterThan("citations", Values.of(100000))
+ val sq5503 = NFEquals("nationality", Values.of("China"))
+ val s5q5012 = NFAnd(sq5501, sq5502)
+ val s5q50123 = NFAnd(s5q5012, sq5503)
+ val n5q50 = "MATCH (n:person) where n.citations>100000 and n.nationality='China' return n"
+
+
+ val sq6501 = NFEquals("labels", Values.of("organization"))
+ val sq6502 = NFGreaterThan("citations", Values.of(2000000))
+ val sq6503 = NFEquals("country", Values.of("China"))
+ val s6q5012 = NFAnd(sq6501, sq6502)
+ val s6q50123 = NFAnd(s6q5012, sq6503)
+ val n6q50 = "MATCH (n:organization) where n.citations>2000000 and n.country='China' RETURN n"
+
+ solrIteratorTime(s4q50123)
+ neo4jTime(n4q50)
+ solrIteratorTime(s5q50123)
+ neo4jTime(n5q50)
+ solrIteratorTime(s6q50123)
+ neo4jTime(n6q50)
+
+ }
+
+ def testLessThan50(): Unit ={
+
+ val sq501 = NFEquals("labels", Values.of("organization"))
+ val sq502 = NFGreaterThan("citations", Values.of(60000000))
+ val sq5012 = NFAnd(sq501, sq502)
+ val s1q50 = sq5012
+ val n1q50 = "match (n:organization) where n.citations>60000000 return n"
+
+ val sq2501 = NFEquals("labels", Values.of("paper"))
+ val sq2502 = NFGreaterThan("citation", Values.of(1500))
+ val s2q50 = NFAnd(sq2501, sq2502)
+ val n2q50 = "MATCH (n:paper) where n.citation >1500 return n"
+
+ val sq3501 = NFEquals("labels", Values.of("person"))
+ val sq3502 = NFGreaterThan("citations", Values.of(400000))
+ val s3q50 = NFAnd(sq3501, sq3502)
+ val n3q50 = "MATCH (n:person) where n.citations>400000 return n"
+
+
+ solrIteratorTime(s1q50)
+ neo4jTime(n1q50)
+ solrIteratorTime(s2q50)
+ neo4jTime(n2q50)
+ solrIteratorTime(s3q50)
+ neo4jTime(n3q50)
+
+ }
+
+ def testForeach(): Unit ={
+
+ val n1 = "match (n) where id(n)=8853096 return n"
+ val s1 = NFEquals("id", Values.of(8853096))
+ val ss1 = "id:8853096"
+
+
+ val n2 = "match (n:organization) where n.citations>985 return n"
+ val s21 = NFGreaterThan("citations", Values.of(985))
+ val s22 = NFEquals("labels", Values.of("organization"))
+ val s2 = NFAnd(s21 , s22)
+ val ss2 = "labels:organization && citations:{ 985 TO *}"
+
+ val n3 = "match (n) where n.nationality='France' return n"
+ val s3 = NFEquals("nationality", Values.of("France"))
+ val ss3 = "nationality:France"
+
+ val n4 = "match (n:organization) return n"
+ val s4 = NFEquals("labels", Values.of("organization"))
+ val ss4 = "labels:organization"
+
+ val n5 = "match (n:person) where n.citations>100 and n.citations5<200 and n.nationality='Russia' return n"
+ val s51 = NFEquals("labels", Values.of("person"))
+ val s52 = NFGreaterThan("citations", Values.of(100))
+ val s53 = NFLessThan("citations5", Values.of(200))
+ val s54 = NFEquals("nationality", Values.of("Russia"))
+ val s512 = NFAnd(s51, s52)
+ val s534 = NFAnd(s53, s54)
+ val s5 = NFAnd(s512, s534)
+ val ss5 = "(labels:person && citations:{ 100 TO * }) && (nationality:Russia && citations5:{ * TO 200 })"
+
+ val n6 = "match (n) where n.citations>100 and n.citations<150 return n"
+ val s61 = NFGreaterThan("citations", Values.of(100))
+ val s62 = NFLessThan("citations", Values.of(150))
+ val s6 = NFAnd(s61, s62)
+ val ss6 = "citations:{ 100 TO 150 }"
+
+ test(s1, n1, ss1)
+ test(s2, n2, ss2)
+ test(s3, n3, ss3)
+ test(s4, n4, ss4)
+ test(s5, n5, ss5)
+ test(s6, n6, ss6)
+
+
+
+ }
+
+ @Test
+ def test1() {
+
+
+ // testForeach()
+ // testFiveFilters()
+ // testLessThan50()
+ // testThreeFilter()
+
+
+ }
+
+}
+
diff --git a/itest/src/test/scala/external-properties/ppd/InEsPredicatePushDown.scala b/itest/src/test/scala/external-properties/ppd/InEsPredicatePushDown.scala
new file mode 100644
index 00000000..a59db301
--- /dev/null
+++ b/itest/src/test/scala/external-properties/ppd/InEsPredicatePushDown.scala
@@ -0,0 +1,30 @@
+package ppd
+
+import java.io.{File, FileInputStream}
+import java.util.Properties
+
+import org.junit.{Before, Test}
+import cn.pandadb.externalprops.{ExternalPropertiesContext, InElasticSearchPropertyNodeStore}
+
+class InEsPredicatePushDown extends QueryCase {
+
+ @Before
+ def init(): Unit = {
+ val configFile = new File("./testdata/neo4j.conf")
+ val props = new Properties()
+ props.load(new FileInputStream(configFile))
+
+ val esHost = props.getProperty("external.properties.store.es.host")
+ val esPort = props.getProperty("external.properties.store.es.port").toInt
+ val esSchema = props.getProperty("external.properties.store.es.schema")
+ val esIndex = props.getProperty("external.properties.store.es.index")
+ val esType = props.getProperty("external.properties.store.es.type")
+ val esScrollSize = props.getProperty("external.properties.store.es.scroll.size", "1000").toInt
+ val esScrollTime = props.getProperty("external.properties.store.es.scroll.time.minutes", "10").toInt
+
+ val esNodeStore = new InElasticSearchPropertyNodeStore(esHost, esPort, esIndex, esType, esSchema, esScrollSize, esScrollTime)
+ esNodeStore.clearAll()
+ buildDB(esNodeStore)
+ }
+
+}
\ No newline at end of file
diff --git a/itest/src/test/scala/external-properties/ppd/InMemoryPredicatePushDown.scala b/itest/src/test/scala/external-properties/ppd/InMemoryPredicatePushDown.scala
new file mode 100644
index 00000000..50604b51
--- /dev/null
+++ b/itest/src/test/scala/external-properties/ppd/InMemoryPredicatePushDown.scala
@@ -0,0 +1,13 @@
+package ppd
+
+import org.junit.Before
+import cn.pandadb.externalprops.InMemoryPropertyNodeStore
+
+class InMemoryPredicatePushDown extends QueryCase {
+
+ @Before
+ def init(): Unit = {
+ buildDB(InMemoryPropertyNodeStore)
+ }
+
+}
\ No newline at end of file
diff --git a/itest/src/test/scala/external-properties/ppd/InSolrPredicatePushDown.scala b/itest/src/test/scala/external-properties/ppd/InSolrPredicatePushDown.scala
new file mode 100644
index 00000000..cfc505fa
--- /dev/null
+++ b/itest/src/test/scala/external-properties/ppd/InSolrPredicatePushDown.scala
@@ -0,0 +1,23 @@
+package ppd
+
+import java.io.{File, FileInputStream}
+import java.util.Properties
+
+import org.junit.Before
+import cn.pandadb.externalprops.{ExternalPropertiesContext, InSolrPropertyNodeStore}
+
+class InSolrPredicatePushDown extends QueryCase {
+
+ @Before
+ def init(): Unit = {
+ val configFile = new File("./testdata/neo4j.conf")
+ val props = new Properties()
+ props.load(new FileInputStream(configFile))
+ val zkString = props.getProperty("external.properties.store.solr.zk")
+ val collectionName = props.getProperty("external.properties.store.solr.collection")
+ val solrNodeStore = new InSolrPropertyNodeStore(zkString, collectionName)
+ solrNodeStore.clearAll()
+ buildDB(solrNodeStore)
+ }
+
+}
\ No newline at end of file
diff --git a/itest/src/test/scala/external-properties/ppd/QueryCase.scala b/itest/src/test/scala/external-properties/ppd/QueryCase.scala
new file mode 100644
index 00000000..8231df5f
--- /dev/null
+++ b/itest/src/test/scala/external-properties/ppd/QueryCase.scala
@@ -0,0 +1,150 @@
+package ppd
+
+import java.io.File
+
+import cn.pandadb.externalprops.{CustomPropertyNodeStore, ExternalPropertiesContext, InMemoryPropertyNodeStore, InMemoryPropertyNodeStoreFactory}
+import cn.pandadb.util.GlobalContext
+import org.junit.{After, Test}
+import org.neo4j.graphdb.GraphDatabaseService
+import org.neo4j.graphdb.factory.GraphDatabaseFactory
+import org.neo4j.io.fs.FileUtils
+
+trait QueryCase {
+
+ var db: GraphDatabaseService = null
+
+ def buildDB(store: CustomPropertyNodeStore): Unit = {
+ if (db == null) {
+ ExternalPropertiesContext.bindCustomPropertyNodeStore(store)
+ GlobalContext.setLeaderNode(true)
+ val dbFile: File = new File("./output/testdb")
+ FileUtils.deleteRecursively(dbFile);
+ dbFile.mkdirs();
+ db = new GraphDatabaseFactory().newEmbeddedDatabaseBuilder(dbFile).newGraphDatabase()
+ db.execute("CREATE (n:Person {age: 10, name: 'bob', address: 'CNIC, CAS, Beijing, China'})")
+ db.execute("CREATE (n:Person {age: 10, name: 'bob2', address: 'CNIC, CAS, Beijing, China'})")
+ db.execute("CREATE (n:Person {age: 40, name: 'alex', address: 'CNIC, CAS, Beijing, China'})")
+ db.execute("CREATE (n:Person {age: 40, name: 'alex2', address: 'CNIC, CAS, Beijing, China'})")
+ db.execute("CREATE INDEX ON :Person(address)")
+ db.execute("CREATE INDEX ON :Person(name)")
+ db.execute("CREATE INDEX ON :Person(age)")
+ db.execute("CREATE INDEX ON :Person(name, age)")
+ db.execute("match (f:Person), (s:Person) where f.age=40 AND s.age=10 CREATE (f)-[hood:Father]->(s)")
+ }
+ }
+
+ @After
+ def shutdownDB(): Unit = {
+ db.shutdown()
+ }
+
+ def testQuery(query: String, resultKey: String): Unit = {
+ val rs = db.execute(query)
+ var resultValue: Long = -1
+ if (rs.hasNext) {
+ resultValue = rs.next().get(resultKey).toString.toLong
+ }
+ assert(resultValue != -1)
+ }
+
+ @Test
+ def lessThan(): Unit = {
+ testQuery("match (n) where 18>n.age return id(n)", "id(n)")
+ }
+
+ @Test
+ def greaterThan(): Unit = {
+ testQuery("match (n) where 9(s:Person) where f.name STARTS WITH 'a' and s.name STARTS WITH 'b' return COUNT(s)", "COUNT(s)")
+ }
+
+ @Test
+ def indexStringEndsWith(): Unit = {
+ testQuery("match (n:Person) USING INDEX n:Person(address, age) where n.address ENDS WITH 'China' and n.age = 10 return id(n)", "id(n)")
+ }
+
+ @Test
+ def compositeIndexStringEndsWith(): Unit = {
+ testQuery("match (n:Person) where n.name = 'bob' and n.age = 10 return count(n)", "count(n)")
+ }
+
+ @Test
+ def udf(): Unit = {
+ testQuery("match (n:Person) where toInteger(n.age) = 10 AND subString(n.address,0,4) = 'CNIC' return id(n)", "id(n)")
+ }
+
+// @Test
+// def notEqual(): Unit = {
+// testQuery("match (f:Person)-[:Father]->(s:Person) where not f.age = s.age return count(f)", "count(f)")
+// }
+
+ @Test
+ def hasProperty(): Unit = {
+ testQuery("match (n:Person) WHERE NOT EXISTS (n.age) return count(n)", "count(n)")
+ }
+
+ @Test
+ def in(): Unit = {
+ testQuery("match (n:Person) WHERE n.age IN [40, 10] return count(n)", "count(n)")
+ }
+
+}
diff --git a/itest/src/test/scala/external-properties/ppd/RelationCase.scala b/itest/src/test/scala/external-properties/ppd/RelationCase.scala
new file mode 100644
index 00000000..892f1b6f
--- /dev/null
+++ b/itest/src/test/scala/external-properties/ppd/RelationCase.scala
@@ -0,0 +1,47 @@
+package ppd
+
+import java.io.{File, FileInputStream}
+import java.util.Properties
+
+import cn.pandadb.externalprops.{ExternalPropertiesContext, InSolrPropertyNodeStore}
+import cn.pandadb.util.GlobalContext
+import org.junit.Test
+import org.neo4j.driver.{AuthTokens, GraphDatabase}
+
+class RelationCase {
+
+ val configFile = new File("./testdata/codeBabyTest.conf")
+ val props = new Properties()
+ props.load(new FileInputStream(configFile))
+ val zkString = props.getProperty("external.properties.store.solr.zk")
+ val collectionName = props.getProperty("external.properties.store.solr.collection")
+
+ val solrNodeStore = new InSolrPropertyNodeStore(zkString, collectionName)
+ GlobalContext.setLeaderNode(true)
+
+ val uri = "bolt://10.0.82.220:7687"
+ val driver = GraphDatabase.driver(uri, AuthTokens.basic("neo4j", "bigdata"))
+ val session = driver.session()
+ val query = "match (n:person)-[:write_paper]->(p:paper) where p.country = 'United States' AND n.citations>10 return count(n)"
+
+ def run(): Unit = {
+ val startTime = System.currentTimeMillis()
+ val result = session.run(query)
+ val endTime = System.currentTimeMillis()
+ println(result.list())
+ println(s"query latency: ${endTime-startTime}")
+ }
+
+ @Test
+ def solr() {
+ ExternalPropertiesContext.bindCustomPropertyNodeStore(solrNodeStore)
+ run()
+ }
+
+ @Test
+ def native(): Unit = {
+ ExternalPropertiesContext.bindCustomPropertyNodeStore(null)
+ run()
+ }
+
+}
diff --git a/itest/src/test/scala/external-properties/query-testbase/QueryTestBase.scala b/itest/src/test/scala/external-properties/query-testbase/QueryTestBase.scala
new file mode 100644
index 00000000..44bbd5f3
--- /dev/null
+++ b/itest/src/test/scala/external-properties/query-testbase/QueryTestBase.scala
@@ -0,0 +1,57 @@
+
+import java.io.{File, FileInputStream}
+import java.util.Properties
+
+import cn.pandadb.externalprops._
+import cn.pandadb.server.PNodeServer
+import cn.pandadb.util.GlobalContext
+import org.junit.{After, Before}
+import org.neo4j.graphdb.GraphDatabaseService
+import org.neo4j.graphdb.factory.GraphDatabaseFactory
+import org.neo4j.io.fs.FileUtils
+
+trait QueryTestBase {
+ var db: GraphDatabaseService = null
+ var nodeStore = "InMemoryPropertyNodeStore"
+
+ @Before
+ def initdb(): Unit = {
+ PNodeServer.toString
+ new File("./output/testdb").mkdirs();
+ FileUtils.deleteRecursively(new File("./output/testdb"));
+ db = new GraphDatabaseFactory().newEmbeddedDatabaseBuilder(new File("./output/testdb")).
+ newGraphDatabase()
+ nodeStore match {
+ case "InMemoryPropertyNodeStore" =>
+ InMemoryPropertyNodeStore.nodes.clear()
+ ExternalPropertiesContext.bindCustomPropertyNodeStore(InMemoryPropertyNodeStore)
+ GlobalContext.setLeaderNode(true)
+
+ case "InSolrPropertyNodeStore" =>
+ val configFile = new File("./testdata/neo4j.conf")
+ val props = new Properties()
+ props.load(new FileInputStream(configFile))
+ val zkString = props.getProperty("external.properties.store.solr.zk")
+ val collectionName = props.getProperty("external.properties.store.solr.collection")
+ val solrNodeStore = new InSolrPropertyNodeStore(zkString, collectionName)
+ solrNodeStore.clearAll()
+ ExternalPropertiesContext.bindCustomPropertyNodeStore(solrNodeStore)
+ case _ =>
+ }
+ }
+
+ @After
+ def shutdowndb(): Unit = {
+ db.shutdown()
+ }
+
+ protected def testQuery[T](query: String): Unit = {
+ val tx = db.beginTx();
+ val rs = db.execute(query);
+ while (rs.hasNext) {
+ val row = rs.next();
+ }
+ tx.success();
+ tx.close()
+ }
+}
diff --git a/itest/src/test/scala/external-properties/solrIteratorPerformanceTest.scala b/itest/src/test/scala/external-properties/solrIteratorPerformanceTest.scala
new file mode 100644
index 00000000..48e26283
--- /dev/null
+++ b/itest/src/test/scala/external-properties/solrIteratorPerformanceTest.scala
@@ -0,0 +1,61 @@
+package external
+
+import java.io.{File, FileInputStream}
+import java.util.Properties
+
+import cn.pandadb.externalprops.{InSolrPropertyNodeStore, SolrQueryResults}
+import org.apache.solr.client.solrj.SolrQuery
+import org.junit.{Assert, Test}
+
+class solrIteratorPerformanceTest {
+
+ val configFile = new File("./testdata/codeBabyTest.conf")
+ val props = new Properties()
+ props.load(new FileInputStream(configFile))
+ val zkString = props.getProperty("external.properties.store.solr.zk")
+ val collectionName = props.getProperty("external.properties.store.solr.collection")
+
+ //scalastyle:off
+ @Test
+ def test1() {
+ println(zkString)
+ println(collectionName)
+
+ val solrNodeStore = new InSolrPropertyNodeStore(zkString, collectionName)
+
+ val size = solrNodeStore.getRecorderSize
+ Assert.assertEquals(13738580, size)
+
+ println(size)
+ var _startTime = System.currentTimeMillis()
+ //val query = new SolrQuery("country:Finland")
+ val query = new SolrQuery("country:Finland")
+ val res = new SolrQueryResults(solrNodeStore._solrClient, query, 10000)
+ val it = res.iterator2().toIterable
+ // it.foreach(u => println(u.id))
+ println(it.size)
+ //it.getCurrentData().foreach(u => println(u))
+ /* println(it.getCurrentData().size)
+ _startTime = System.currentTimeMillis()
+ var i = 0
+ while (it.readNextPage()) {
+ i += 1
+ val _endTime = System.currentTimeMillis()
+ println({s"$i--Time:${_endTime - _startTime}"})
+ //it.getCurrentData().foreach(u => println(u))
+ println(it.getCurrentData().size)
+ }
+ val _endTime = System.currentTimeMillis()
+
+ println({s"iterator-totalTime:${_endTime - _startTime}"})
+
+ // res.getAllResults()
+
+ val _endTime1 = System.currentTimeMillis()
+
+ println({s"getall-totalTime:${_endTime1 - _endTime}"})
+*/
+
+ }
+
+}
diff --git a/itest/src/test/scala/perfomance/PerformanceTests.scala b/itest/src/test/scala/perfomance/PerformanceTests.scala
new file mode 100644
index 00000000..67424c1c
--- /dev/null
+++ b/itest/src/test/scala/perfomance/PerformanceTests.scala
@@ -0,0 +1,158 @@
+package perfomance
+
+import java.io.{File, FileInputStream, FileWriter}
+import java.text.SimpleDateFormat
+import java.util.{Date, Properties}
+
+import cn.pandadb.externalprops.{ExternalPropertiesContext, InElasticSearchPropertyNodeStore}
+import cn.pandadb.util.GlobalContext
+import org.neo4j.graphdb.GraphDatabaseService
+import org.neo4j.graphdb.factory.GraphDatabaseFactory
+
+import scala.io.Source
+
+
+trait TestBase {
+
+ def nowDate: String = {
+ val now = new Date
+ val dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
+ dateFormat.format(now)
+ }
+
+}
+
+
+object Neo4jTests extends TestBase {
+
+ var esNodeStores: Option[InElasticSearchPropertyNodeStore] = None
+
+ def createPandaDB(props: Properties): GraphDatabaseService = {
+ var graphPath = ""
+ if (props.containsKey("graph.data.path")) graphPath = props.get("graph.data.path").toString
+ else throw new Exception("Configure File Error: graph.data.path is not exist! ")
+ val graphFile = new File(graphPath)
+ if (!graphFile.exists) throw new Exception(String.format("Error: GraphPath(%s) is not exist! ", graphPath))
+
+ val esHost = props.getProperty("external.properties.store.es.host")
+ val esPort = props.getProperty("external.properties.store.es.port").toInt
+ val esSchema = props.getProperty("external.properties.store.es.schema")
+ val esIndex = props.getProperty("external.properties.store.es.index")
+ val esType = props.getProperty("external.properties.store.es.type")
+ val esScrollSize = props.getProperty("external.properties.store.es.scroll.size", "1000").toInt
+ val esScrollTime = props.getProperty("external.properties.store.es.scroll.time.minutes", "10").toInt
+ val esNodeStore = new InElasticSearchPropertyNodeStore(esHost, esPort, esIndex, esType, esSchema, esScrollSize, esScrollTime)
+ ExternalPropertiesContext.bindCustomPropertyNodeStore(esNodeStore)
+ GlobalContext.setLeaderNode(true)
+ esNodeStores = Some(esNodeStore)
+
+ new GraphDatabaseFactory().newEmbeddedDatabase(graphFile)
+ }
+
+ def createNeo4jDB(props: Properties): GraphDatabaseService = {
+ var graphPath = ""
+ if (props.containsKey("graph.data.path")) graphPath = props.get("graph.data.path").toString
+ else throw new Exception("Configure File Error: graph.data.path is not exist! ")
+ val graphFile = new File(graphPath)
+ if (!graphFile.exists) throw new Exception(String.format("Error: GraphPath(%s) is not exist! ", graphPath))
+
+ new GraphDatabaseFactory().newEmbeddedDatabase(graphFile)
+ }
+
+ def main(args: Array[String]): Unit = {
+
+ var propFilePath = "/home/bigdata/pandadb-2019/itest/testdata/performance-test.conf" // null;
+ if (args.length > 0) propFilePath = args(0)
+ val props = new Properties
+ props.load(new FileInputStream(new File(propFilePath)))
+
+ var testDb = "neo4j"
+ var graphPath = ""
+ var logFileDir = ""
+ var cyhperFilePath = ""
+
+ if (props.containsKey("test.db")) testDb = props.get("test.db").toString.toLowerCase()
+ else throw new Exception("Configure File Error: test.db is not exist! ")
+
+ if (props.containsKey("graph.data.path")) graphPath = props.get("graph.data.path").toString
+ else throw new Exception("Configure File Error: graph.data.path is not exist! ")
+
+ if (props.containsKey("log.file.dir")) logFileDir = props.get("log.file.dir").toString
+ else throw new Exception("Configure File Error: log.file.dir is not exist! ")
+
+ if (props.containsKey("test.cyhper.path")) cyhperFilePath = props.get("test.cyhper.path").toString
+ else throw new Exception("Configure File Error: test.cyhper.path is not exist! ")
+
+ val logDir: File = new File(logFileDir)
+ if (!logDir.exists()) {
+ logDir.mkdirs
+ println("make log dir")
+ }
+ val logFileName = new SimpleDateFormat("MMdd-HHmmss").format(new Date) + ".log"
+ val logFile = new File(logDir, logFileName)
+
+ println("Neo4j Test")
+ println(s"GraphDataPath: ${graphPath} \n LogFilePath: ${logFile.getAbsolutePath}")
+
+ val logFw = new FileWriter(logFile)
+ logFw.write(s"GraphDataPath: $graphPath \n")
+ val cyhpers = readCyphers(cyhperFilePath)
+ var db: GraphDatabaseService = null
+
+ if (testDb.equals("neo4j")) {
+ println(s"testDB: neo4j \n")
+ logFw.write(s"testDB: neo4j \n")
+ db = createNeo4jDB(props)
+ }
+ else if (testDb.equals("pandadb")) {
+ println(s"testDB: pandadb \n")
+ logFw.write(s"testDB: pandadb \n")
+ db = createPandaDB(props)
+ }
+
+ if (db == null) {
+ throw new Exception("DB is null")
+ }
+
+ println("==== begin tests ====")
+ val beginTime = nowDate
+ println(beginTime)
+
+ try {
+ var i = 0
+ cyhpers.foreach(cyhper => {
+ i += 1
+ val tx = db.beginTx()
+ val mills0 = System.currentTimeMillis()
+ val res = db.execute(cyhper)
+ val useMills = System.currentTimeMillis() - mills0
+ tx.close()
+ println(s"$i, $useMills")
+ logFw.write(s"\n====\n$cyhper\n")
+ logFw.write(s"UsedTime(ms): $useMills \n")
+ logFw.flush()
+ })
+ }
+ finally {
+ logFw.close()
+ if (testDb == "pandadb" && esNodeStores.isDefined) {
+ esNodeStores.get.esClient.close()
+ }
+ db.shutdown()
+ }
+
+ println("==== end tests ====")
+ val endTime = nowDate
+ println("Begin Time: " + beginTime)
+ println("End Time: " + endTime)
+
+ }
+
+ def readCyphers(filePath: String): Iterable[String] = {
+ val source = Source.fromFile(filePath, "UTF-8")
+ val lines = source.getLines().toArray
+ source.close()
+ lines
+ }
+
+}
diff --git a/itest/src/test/scala/release/EnvironmentTest.scala b/itest/src/test/scala/release/EnvironmentTest.scala
new file mode 100644
index 00000000..151ee265
--- /dev/null
+++ b/itest/src/test/scala/release/EnvironmentTest.scala
@@ -0,0 +1,73 @@
+package release
+
+import java.io.{File, FileInputStream}
+import java.util.Properties
+
+import cn.pandadb.network.{NodeAddress, ZKPathConfig}
+import org.apache.curator.framework.{CuratorFramework, CuratorFrameworkFactory}
+import org.apache.curator.retry.ExponentialBackoffRetry
+import org.junit.{Assert, Test}
+import EnvironmentTest.{clusterNodes, curator}
+import org.neo4j.driver.{AuthTokens, GraphDatabase}
+
+/**
+ * @Author: Airzihao
+ * @Description: This is a comprehensive test for the environment.
+ * @Date: Created at 10:24 2019/12/20
+ * @Modified By:
+ */
+
+object EnvironmentTest {
+
+ val dir = new File("./itest/comprehensive")
+ if (!dir.exists()) {
+ dir.mkdirs()
+ }
+ val props: Properties = {
+ val props = new Properties()
+ props.load(new FileInputStream(new File(s"${dir}/envirTest.properties")))
+ props
+ }
+ val zkString = props.getProperty("zkServerAddr")
+ val clusterNodes: Array[NodeAddress] = {
+ props.getProperty("clusterNodes").split(",").map(str => NodeAddress.fromString(str))
+ }
+ val curator: CuratorFramework = CuratorFrameworkFactory.newClient(zkString,
+ new ExponentialBackoffRetry(1000, 3));
+ curator.start()
+}
+
+// shall this class be established on the driver side?
+class EnvironmentTest {
+
+ // test zk environment, make sure the cluster has the access to R/W the ZK cluster
+ @Test
+ def test1(): Unit = {
+ if (curator.checkExists().forPath(ZKPathConfig.registryPath) == null) {
+ curator.create().forPath(ZKPathConfig.registryPath)
+ }
+ curator.delete().deletingChildrenIfNeeded().forPath(ZKPathConfig.registryPath)
+ Assert.assertEquals(null, curator.checkExists().forPath(ZKPathConfig.registryPath))
+ }
+
+ // make sure the driver can access to each node.
+ @Test
+ def test2(): Unit = {
+ clusterNodes.foreach(nodeAddress => {
+ val boltURI = s"bolt://${nodeAddress.getAsString}"
+ val driver = GraphDatabase.driver(boltURI, AuthTokens.basic("", ""))
+ val session = driver.session()
+ val tx = session.beginTransaction()
+ tx.success()
+ tx.close()
+ session.close()
+ driver.close()
+ })
+ }
+
+ // how to make sure the node has access to each other?
+ @Test
+ def test3(): Unit = {
+
+ }
+}
diff --git a/itest/src/test/scala/release/PerformanceTest.scala b/itest/src/test/scala/release/PerformanceTest.scala
new file mode 100644
index 00000000..179d8942
--- /dev/null
+++ b/itest/src/test/scala/release/PerformanceTest.scala
@@ -0,0 +1,254 @@
+package release
+
+import java.io.{File, FileInputStream, PrintWriter}
+import java.util.Properties
+import java.util.concurrent.TimeoutException
+
+import cn.pandadb.driver.{EASY_ROUND, RANDOM_PICK, ROBIN_ROUND, SelectNode}
+import com.google.gson.GsonBuilder
+import org.junit.{Assert, Test}
+import org.neo4j.driver.{AuthTokens, Driver, GraphDatabase, StatementResult}
+
+import scala.collection.mutable.ListBuffer
+import scala.concurrent.ExecutionContext.Implicits.global
+import scala.concurrent.duration._
+import scala.concurrent.{Await, Future}
+import scala.io.Source
+
+/**
+ * @Author: Airzihao
+ * @Description:
+ * @Date: Created at 11:40 2019/12/11
+ * @Modified By:
+ */
+abstract class PerformanceTest {
+
+ val dir = new File("./itest/performance")
+ if (!dir.exists()) {
+ dir.mkdirs()
+ }
+
+ val outputDir = new File(s"${dir}/output")
+ if (!outputDir.exists()) {
+ outputDir.mkdirs()
+ }
+
+ val gson = new GsonBuilder().enableComplexMapKeySerialization().create()
+
+ val props: Properties = {
+ val props = new Properties()
+ props.load(new FileInputStream(new File(s"${dir}/performanceConf.properties")))
+ props
+ }
+
+ def getRecordFile(fileName: String): File = {
+ val recordFile = new File(s"${outputDir}/${fileName}")
+ if(!recordFile.exists()) {
+ recordFile.createNewFile()
+ }
+ recordFile
+ }
+
+ def getStatementsIter(fileName: String): Iterator[String] = {
+ val statementFile = new File(s"${dir}/${fileName}")
+ val source = Source.fromFile(statementFile, "utf-8")
+ val lineIterator = source.getLines()
+ lineIterator
+ }
+
+ def executeCypher[T <: Driver](cypher: String, driver: T): (Array[Long], StatementResult) = {
+ val _time0 = System.currentTimeMillis()
+
+ val session = driver.session()
+ val _time1 = System.currentTimeMillis()
+
+ val tx = session.beginTransaction()
+ val _time2 = System.currentTimeMillis()
+
+ val ans = tx.run(cypher)
+ val _time3 = System.currentTimeMillis()
+
+ tx.success()
+ tx.close()
+ val _time4 = System.currentTimeMillis()
+
+ session.close()
+ val _time5 = System.currentTimeMillis()
+ (Array(_time0, _time1, _time2, _time3, _time4, _time5), ans)
+ }
+
+ def fullTest(recordFile: File, recorder: PrintWriter, cmdIter: Iterator[String], driverArgs: Array[String]): Map[String, StatementResult] = {
+ var ansMap: Map[String, StatementResult] = Map()
+
+ val cmdArray = cmdIter.toArray
+ val _startTime = System.currentTimeMillis()
+ val resultLog = new ListBuffer[Future[ResultMap]]
+
+ cmdArray.foreach(cypher => {
+ val logItem = Future[ResultMap] {
+ val driver = GraphDatabase.driver(driverArgs(0), AuthTokens.basic(driverArgs(1), driverArgs(2)))
+ val result = executeCypher(cypher, driver)
+ val resultMap = new ResultMap(cypher, result._1)
+ ansMap += (cypher -> result._2)
+ resultMap
+ }
+ resultLog.append(logItem)
+ })
+
+ var _i = 0
+ var _successed = 0
+ var _failed = 0
+ val sum = resultLog.length
+ resultLog.foreach(logItem => {
+ val resultMap: ResultMap = try {
+ _i = _i + 1
+ // scalastyle:off
+ println(s"Waiting for the ${_i}th of ${sum} result, ${_successed} successed, ${_failed} timeout.")
+ val resultMap = Await.result(logItem, 300.seconds)
+ _successed += 1
+ resultMap
+ } catch {
+ case timeout: TimeoutException =>
+ _failed += 1
+ val _timeOutArray = Array(-1.toLong, -1.toLong, -1.toLong, -1.toLong, -1.toLong, -1.toLong)
+ val cypher = cmdArray(_i-1)
+ new ResultMap(cypher, _timeOutArray)
+ }
+ val line = gson.toJson(resultMap.getResultMap) + "\n"
+ recorder.write(line)
+ recorder.flush()
+ })
+ val _endTime = System.currentTimeMillis()
+ recorder.write({s"totalTime:${_endTime - _startTime}"})
+ recorder.flush()
+ ansMap
+ }
+
+ def fullTest(recordFile: File, recorder: PrintWriter, cmdIter: Iterator[String], driver: Driver): Map[String, StatementResult] = {
+ var ansMap: Map[String, StatementResult] = Map()
+
+ val cmdArray = cmdIter.toArray
+ val _startTime = System.currentTimeMillis()
+ val resultLog = new ListBuffer[Future[ResultMap]]
+
+ cmdArray.foreach(cypher => {
+ val logItem = Future[ResultMap] {
+ val result = executeCypher(cypher, driver)
+ val resultMap = new ResultMap(cypher, result._1)
+ ansMap += (cypher -> result._2)
+ resultMap
+ }
+ resultLog.append(logItem)
+ })
+
+ var _i = 0
+ var _successed = 0
+ var _failed = 0
+ val sum = resultLog.length
+ resultLog.foreach(logItem => {
+ val resultMap: ResultMap = try {
+ _i = _i + 1
+ // scalastyle:off
+ println(s"Waiting for the ${_i}th of ${sum} result, ${_successed} successed, ${_failed} timeout.")
+ val resultMap = Await.result(logItem, 300.seconds)
+ _successed += 1
+ resultMap
+ } catch {
+ case timeout: TimeoutException =>
+ _failed += 1
+ val _timeOutArray = Array(-1.toLong, -1.toLong, -1.toLong, -1.toLong, -1.toLong, -1.toLong)
+ val cypher = cmdArray(_i-1)
+ new ResultMap(cypher, _timeOutArray)
+ }
+ val line = gson.toJson(resultMap.getResultMap) + "\n"
+ recorder.write(line)
+ recorder.flush()
+ })
+ val _endTime = System.currentTimeMillis()
+ recorder.write({s"totalTime:${_endTime - _startTime}"})
+ recorder.flush()
+ ansMap
+ }
+
+}
+
+class Neo4jPerformanceTest extends PerformanceTest {
+
+ val recordFile = getRecordFile(props.getProperty("neo4jResultFile"))
+ val recorder = new PrintWriter(recordFile)
+ val cmdIter = getStatementsIter(props.getProperty("statementsFile"))
+
+ val driver = GraphDatabase.driver(props.getProperty("boltURI"),
+ AuthTokens.basic("neo4j", "bigdata"))
+
+ @Test
+ def test1(): Unit = {
+ fullTest(recordFile, recorder, cmdIter, driver)
+ }
+
+}
+
+class PandaDBPerformanceTest extends PerformanceTest {
+ val recordFile = getRecordFile(props.getProperty("PandaDBResultFile"))
+ val recorder = new PrintWriter(recordFile)
+ val cmdIter = getStatementsIter(props.getProperty("statementsFile"))
+// SelectNode.setPolicy(ne)
+ SelectNode.setPolicy(new EASY_ROUND)
+ val pandaDriver = GraphDatabase.driver(s"panda://${props.getProperty("zkServerAddr")}/db",
+ AuthTokens.basic("", ""))
+
+ @Test
+ def test1(): Unit = {
+ fullTest(recordFile, recorder, cmdIter, pandaDriver)
+ }
+}
+
+class MergePerformanceTest extends PandaDBPerformanceTest {
+
+ // just run test0, you will get both neo4j and panda test result
+ @Test
+ def test0(): Unit = {
+ val list = List(2)
+ list.foreach(i => circularTest(i))
+ }
+
+ def circularTest(time: Int): Unit = {
+ val pandaResult = pandaTest(time)
+ Thread.sleep(5000)
+ val neo4jResult = neo4jTest(time)
+// Await.result(pandaResult, Duration.Inf)
+// Await.result(neo4jResult, Duration.Inf)
+ neo4jResult.foreach( r => {
+ val n = r._2
+ println(r._1)
+ if(pandaResult.contains(r._1)) {
+ val p = pandaResult.get(r._1).get
+ Assert.assertEquals(n.hasNext, p.hasNext)
+ while (n.hasNext) {
+ Assert.assertEquals(n.next(), p.next())
+ }
+ Assert.assertEquals(n.hasNext, p.hasNext)
+ }
+ })
+ }
+
+ def pandaTest(time: Int): Map[String, StatementResult] = {
+ val pRecordFile = getRecordFile(s"hugepanda${time}.txt")
+ val pRecorder = new PrintWriter(pRecordFile)
+ val pCmdIter = getStatementsIter(props.getProperty("statementsFile"))
+ SelectNode.setPolicy(new EASY_ROUND)
+ val pandaDriver = GraphDatabase.driver(s"panda://${props.getProperty("zkServerAddr")}/db",
+ AuthTokens.basic("", ""))
+// fullTest(pRecordFile, pRecorder, pCmdIter, Array(s"panda://${props.getProperty("zkServerAddr")}/db", "", ""))
+ fullTest(pRecordFile, pRecorder, pCmdIter, pandaDriver)
+ }
+
+ def neo4jTest(time: Int): Map[String, StatementResult] = {
+ val nRecordFile = getRecordFile(s"hugeneo4j${time}.txt")
+ val nRecorder = new PrintWriter(nRecordFile)
+ val nCmdIter = getStatementsIter(props.getProperty("statementsFile"))
+// val neo4jDriver = GraphDatabase.driver(props.getProperty("boltURI"),
+// AuthTokens.basic("neo4j", "bigdata"))
+ fullTest(nRecordFile, nRecorder, nCmdIter, Array(props.getProperty("boltURI"), "neo4j", "bigdata"))
+ }
+}
\ No newline at end of file
diff --git a/itest/src/test/scala/release/ResultMap.scala b/itest/src/test/scala/release/ResultMap.scala
new file mode 100644
index 00000000..f5439371
--- /dev/null
+++ b/itest/src/test/scala/release/ResultMap.scala
@@ -0,0 +1,63 @@
+package release
+
+import scala.collection.JavaConverters._
+import scala.collection.mutable
+
+/**
+ * @Author: Airzihao
+ * @Description:
+ * @Date: Created at 16:33 2019/12/11
+ * @Modified By:
+ */
+
+class ResultMap(cypher: String, timeList: Array[Long]) {
+
+ private val _resultMap = mutable.Map[String, Any]()
+
+ def put[T](key: String, value: T): T = {
+ _resultMap(key) = value
+ value
+ };
+
+ def getResultMap: java.util.Map[String, Any] = {
+ this._putCypher
+ if (timeList.sum == -6) {
+ _resultMap += ("sessionCreation" -> -1)
+ _resultMap += ("txCreation" -> -1)
+ _resultMap += ("executionTime" -> -1)
+ _resultMap += ("txClose" -> -1)
+ _resultMap += ("sessionClose" -> -1)
+ _resultMap += ("totalRespTime" -> -1)
+ } else {
+ this._putSessionCreationTime
+ this._putTxCreationTime
+ this._putExecutionTime
+ this._putTxCloseTime
+ this._putSessionCloseTime
+ this._putRespTime
+ }
+ _resultMap.toMap.asJava
+ }
+
+ private def _putCypher: Unit = {
+ this.put("cypher", cypher)
+ }
+ private def _putSessionCreationTime: Unit = {
+ this.put("sessionCreation", (timeList(1) - timeList(0)).toInt)
+ }
+ private def _putTxCreationTime: Unit = {
+ this.put("txCreation", (timeList(2) - timeList(1)).toInt)
+ }
+ private def _putExecutionTime: Unit = {
+ this.put("executionTime", (timeList(3) - timeList(2)).toInt)
+ }
+ private def _putTxCloseTime: Unit = {
+ this.put("txClose", (timeList(4) - timeList(3)).toInt)
+ }
+ private def _putSessionCloseTime: Unit = {
+ this.put("sessionClose", (timeList(5) - timeList(4)).toInt)
+ }
+ private def _putRespTime: Unit = {
+ this.put("totalRespTime", (timeList(5) - timeList(0)).toInt)
+ }
+}
diff --git a/testdata/car1.jpg b/itest/testdata/car1.jpg
similarity index 100%
rename from testdata/car1.jpg
rename to itest/testdata/car1.jpg
diff --git a/itest/testdata/codeBabyTest.conf b/itest/testdata/codeBabyTest.conf
new file mode 100644
index 00000000..b63eba37
--- /dev/null
+++ b/itest/testdata/codeBabyTest.conf
@@ -0,0 +1,40 @@
+dbms.security.auth_enabled=false
+dbms.connector.bolt.enabled=true
+dbms.connector.bolt.tls_level=OPTIONAL
+dbms.connector.bolt.listen_address=0.0.0.0:7687
+
+dbms.connector.http.enabled=true
+dbms.connector.http.listen_address=localhost:7474
+dbms.connector.https.enabled=false
+dbms.logs.http.enabled=true
+
+blob.plugins.conf=./cypher-plugins.xml
+
+#blob.storage=cn.pidb.engine.HBaseBlobValueStorage
+blob.storage.hbase.zookeeper.port=2181
+blob.storage.hbase.zookeeper.quorum=localhost
+blob.storage.hbase.auto_create_table=true
+blob.storage.hbase.table=PIDB_BLOB
+
+blob.aipm.modules.enabled=false
+blob.aipm.modules.dir=/usr/local/aipm/modules/
+
+#blob.storage=cn.pidb.engine.FileBlobValueStorage
+#blob.storage.file.dir=/tmp
+
+#dbms.active_database=testdb
+aipm.http.host.url=http://10.0.86.128:8081/
+external.properties.store.factory=cn.pandadb.externalprops.InSolrPropertyNodeStoreFactory
+#external.properties.store.solr.zk=10.0.86.179:2181,10.0.87.45:2181,10.0.87.46:2181
+#external.properties.store.solr.collection=graiphdb
+
+
+#external.property.storage.enabled=true
+#external.properties.store.factory=org.neo4j.kernel.impl.InSolrPropertyNodeStoreFactory
+external.properties.store.solr.zk=10.0.82.216:2181,10.0.82.217:2181,10.0.82.218:2181
+external.properties.store.solr.collection=panda-1300W
+
+#zookeeper.address=10.0.82.216:2181,10.0.82.217:2181
+#node.server.address=10.0.82.216:7685
+#localIpAddress=10.0.82.216
+#rpcPort=1224
diff --git a/itest/testdata/gnode0.conf b/itest/testdata/gnode0.conf
new file mode 100644
index 00000000..a87289cc
--- /dev/null
+++ b/itest/testdata/gnode0.conf
@@ -0,0 +1,32 @@
+dbms.security.auth_enabled=false
+dbms.connector.bolt.enabled=true
+dbms.connector.bolt.tls_level=OPTIONAL
+dbms.connector.bolt.listen_address=0.0.0.0:7685
+
+dbms.connector.http.enabled=true
+dbms.connector.http.listen_address=0.0.0.0:7469
+dbms.connector.https.enabled=false
+dbms.logs.http.enabled=true
+
+blob.plugins.conf=./cypher-plugins.xml
+
+#blob.storage=cn.pidb.engine.HBaseBlobValueStorage
+blob.storage.hbase.zookeeper.port=2181
+blob.storage.hbase.zookeeper.quorum=localhost
+blob.storage.hbase.auto_create_table=true
+blob.storage.hbase.table=PIDB_BLOB
+
+blob.aipm.modules.enabled=false
+blob.aipm.modules.dir=/usr/local/aipm/modules/
+
+#blob.storage=cn.pidb.engine.FileBlobValueStorage
+#blob.storage.file.dir=/tmp
+
+#dbms.active_database=testdb
+aipm.http.host.url=http://10.0.86.128:8081/
+
+#zookeeper.address=10.0.86.26:2181,10.0.86.27:2181,10.0.86.70:2181
+zookeeper.address=10.0.82.216:2181
+node.server.address=0.0.0.0:7685
+localIpAddress=0.0.0.0
+rpc.port=1224
\ No newline at end of file
diff --git a/testdata/neo4j.conf b/itest/testdata/gnode1.conf
similarity index 70%
rename from testdata/neo4j.conf
rename to itest/testdata/gnode1.conf
index 40a521d8..56403bf6 100644
--- a/testdata/neo4j.conf
+++ b/itest/testdata/gnode1.conf
@@ -1,10 +1,11 @@
dbms.security.auth_enabled=false
dbms.connector.bolt.enabled=true
dbms.connector.bolt.tls_level=OPTIONAL
-dbms.connector.bolt.listen_address=:7687
+dbms.connector.bolt.listen_address=0.0.0.0:7686
dbms.connector.http.enabled=true
-dbms.connector.http.listen_address=localhost:7474
+dbms.connector.http.listen_address=0.0.0.0:7470
+dbms.connector.https.enabled=false
dbms.logs.http.enabled=true
blob.plugins.conf=./cypher-plugins.xml
@@ -23,3 +24,8 @@ blob.aipm.modules.dir=/usr/local/aipm/modules/
#dbms.active_database=testdb
aipm.http.host.url=http://10.0.86.128:8081/
+
+zookeeper.address=10.0.86.26:2181,10.0.86.27:2181,10.0.86.70:2181
+node.server.address=10.0.87.7:7686
+localIpAddress=10.0.87.7
+rpcPort=1225
\ No newline at end of file
diff --git a/itest/testdata/gnode2.conf b/itest/testdata/gnode2.conf
new file mode 100644
index 00000000..40c95497
--- /dev/null
+++ b/itest/testdata/gnode2.conf
@@ -0,0 +1,31 @@
+dbms.security.auth_enabled=false
+dbms.connector.bolt.enabled=true
+dbms.connector.bolt.tls_level=OPTIONAL
+dbms.connector.bolt.listen_address=0.0.0.0:7685
+
+dbms.connector.http.enabled=true
+dbms.connector.http.listen_address=0.0.0.0:7469
+dbms.connector.https.enabled=false
+dbms.logs.http.enabled=true
+
+blob.plugins.conf=./cypher-plugins.xml
+
+#blob.storage=cn.pidb.engine.HBaseBlobValueStorage
+blob.storage.hbase.zookeeper.port=2181
+blob.storage.hbase.zookeeper.quorum=localhost
+blob.storage.hbase.auto_create_table=true
+blob.storage.hbase.table=PIDB_BLOB
+
+blob.aipm.modules.enabled=false
+blob.aipm.modules.dir=/usr/local/aipm/modules/
+
+#blob.storage=cn.pidb.engine.FileBlobValueStorage
+#blob.storage.file.dir=/tmp
+
+#dbms.active_database=testdb
+aipm.http.host.url=http://10.0.86.128:8081/
+
+zookeeper.address=10.0.86.26:2181,10.0.86.27:2181,10.0.86.70:2181
+node.server.address=10.0.87.9:7685
+localIpAddress=10.0.87.7
+rpcPort=1226
\ No newline at end of file
diff --git a/itest/testdata/gnode3.conf b/itest/testdata/gnode3.conf
new file mode 100644
index 00000000..502b84e8
--- /dev/null
+++ b/itest/testdata/gnode3.conf
@@ -0,0 +1,31 @@
+dbms.security.auth_enabled=false
+dbms.connector.bolt.enabled=true
+dbms.connector.bolt.tls_level=OPTIONAL
+dbms.connector.bolt.listen_address=0.0.0.0:7686
+
+dbms.connector.http.enabled=true
+dbms.connector.http.listen_address=0.0.0.0:7470
+dbms.connector.https.enabled=false
+dbms.logs.http.enabled=true
+
+blob.plugins.conf=./cypher-plugins.xml
+
+#blob.storage=cn.pidb.engine.HBaseBlobValueStorage
+blob.storage.hbase.zookeeper.port=2181
+blob.storage.hbase.zookeeper.quorum=localhost
+blob.storage.hbase.auto_create_table=true
+blob.storage.hbase.table=PIDB_BLOB
+
+blob.aipm.modules.enabled=false
+blob.aipm.modules.dir=/usr/local/aipm/modules/
+
+#blob.storage=cn.pidb.engine.FileBlobValueStorage
+#blob.storage.file.dir=/tmp
+
+#dbms.active_database=testdb
+aipm.http.host.url=http://10.0.86.128:8081/
+
+zookeeper.address=10.0.86.26:2181,10.0.86.27:2181,10.0.86.70:2181
+node.server.address=10.0.87.9:7686
+localIpAddress=10.0.87.7
+rpcPort=1227
\ No newline at end of file
diff --git a/itest/testdata/localnode0.conf b/itest/testdata/localnode0.conf
new file mode 100644
index 00000000..35319a86
--- /dev/null
+++ b/itest/testdata/localnode0.conf
@@ -0,0 +1,36 @@
+dbms.security.auth_enabled=false
+dbms.connector.bolt.enabled=true
+dbms.connector.bolt.tls_level=OPTIONAL
+dbms.connector.bolt.listen_address=0.0.0.0:7684
+
+dbms.connector.http.enabled=true
+dbms.connector.http.listen_address=0.0.0.0:7468
+dbms.connector.https.enabled=false
+dbms.logs.http.enabled=true
+
+blob.plugins.conf=./cypher-plugins.xml
+
+#blob.storage=cn.pidb.engine.HBaseBlobValueStorage
+blob.storage.hbase.zookeeper.port=2181
+blob.storage.hbase.zookeeper.quorum=localhost
+blob.storage.hbase.auto_create_table=true
+blob.storage.hbase.table=PIDB_BLOB
+
+blob.aipm.modules.enabled=false
+blob.aipm.modules.dir=/usr/local/aipm/modules/
+
+#blob.storage=cn.pidb.engine.FileBlobValueStorage
+#blob.storage.file.dir=/tmp
+
+#dbms.active_database=testdb
+aipm.http.host.url=http://10.0.86.128:8081/
+
+zookeeper.address=10.0.82.216:2181
+localIpAddress=159.226.193.204
+node.server.address=159.226.193.204:7684
+rpc.port=1224
+
+#external.properties.store.factory=cn.pandadb.externalprops.InSolrPropertyNodeStoreFactory
+#external.properties.store.solr.zk=10.0.82.216:2181,10.0.82.217:2181,10.0.82.218:2181
+#external.properties.store.solr.collection=panda-1300W
+#external.property.storage.enabled=true
\ No newline at end of file
diff --git a/testdata/lqd.jpeg b/itest/testdata/lqd.jpeg
similarity index 100%
rename from testdata/lqd.jpeg
rename to itest/testdata/lqd.jpeg
diff --git a/testdata/mayun1.jpeg b/itest/testdata/mayun1.jpeg
similarity index 100%
rename from testdata/mayun1.jpeg
rename to itest/testdata/mayun1.jpeg
diff --git a/testdata/mayun2.jpeg b/itest/testdata/mayun2.jpeg
similarity index 100%
rename from testdata/mayun2.jpeg
rename to itest/testdata/mayun2.jpeg
diff --git a/itest/testdata/neo4j.conf b/itest/testdata/neo4j.conf
new file mode 100644
index 00000000..7fe8aa82
--- /dev/null
+++ b/itest/testdata/neo4j.conf
@@ -0,0 +1,39 @@
+dbms.security.auth_enabled=false
+dbms.connector.bolt.enabled=true
+dbms.connector.bolt.tls_level=OPTIONAL
+dbms.connector.bolt.listen_address=0.0.0.0:7687
+
+dbms.connector.http.enabled=true
+dbms.connector.http.listen_address=localhost:7474
+dbms.connector.https.enabled=false
+dbms.logs.http.enabled=true
+
+blob.plugins.conf=./cypher-plugins.xml
+
+#blob.storage=cn.pidb.engine.HBaseBlobValueStorage
+blob.storage.hbase.zookeeper.port=2181
+blob.storage.hbase.zookeeper.quorum=localhost
+blob.storage.hbase.auto_create_table=true
+blob.storage.hbase.table=PIDB_BLOB
+
+blob.aipm.modules.enabled=false
+blob.aipm.modules.dir=/usr/local/aipm/modules/
+
+#blob.storage=cn.pidb.engine.FileBlobValueStorage
+#blob.storage.file.dir=/tmp
+
+#dbms.active_database=testdb
+aipm.http.host.url=http://10.0.86.128:8081/
+external.properties.store.factory= org.neo4j.kernel.impl.InSolrPropertyNodeStoreFactory
+external.properties.store.solr.zk=10.0.86.179:2181,10.0.87.45:2181,10.0.87.46:2181
+external.properties.store.solr.collection=graiphdb
+
+# ElasticSearch store
+external.properties.store.factory= org.neo4j.kernel.impl.InElasticSearchPropertyNodeStoreFactory
+external.properties.store.es.host=10.0.82.218
+external.properties.store.es.port=9200
+external.properties.store.es.schema=http
+external.properties.store.es.scroll.size=1000
+external.properties.store.es.scroll.time.minutes=10
+external.properties.store.es.index=test-0119
+external.properties.store.es.type=nodes
diff --git a/itest/testdata/performance-test.conf b/itest/testdata/performance-test.conf
new file mode 100644
index 00000000..59f19596
--- /dev/null
+++ b/itest/testdata/performance-test.conf
@@ -0,0 +1,16 @@
+# test: neo4j/pandadb
+test.db=pandadb
+test.cyhper.path=/home/bigdata/pandadb-2019/itest/testdata/cyhper.txt
+graph.data.path=/home/bigdata/panda_output/graph.db1
+log.file.dir=/home/bigdata/pandadb-2019/itest/test-logs/
+
+
+# ElasticSearch store
+external.properties.store.factory=org.neo4j.kernel.impl.InElasticSearchPropertyNodeStoreFactory
+external.properties.store.es.host=10.0.82.216
+external.properties.store.es.port=9200
+external.properties.store.es.schema=http
+external.properties.store.es.scroll.size=1000
+external.properties.store.es.scroll.time.minutes=10
+external.properties.store.es.index=pandadb
+external.properties.store.es.type=nodes
diff --git a/testdata/test.csv b/itest/testdata/test.csv
similarity index 100%
rename from testdata/test.csv
rename to itest/testdata/test.csv
diff --git a/testdata/test.png b/itest/testdata/test.png
similarity index 100%
rename from testdata/test.png
rename to itest/testdata/test.png
diff --git a/testdata/test.wav b/itest/testdata/test.wav
similarity index 100%
rename from testdata/test.wav
rename to itest/testdata/test.wav
diff --git a/testdata/test1.png b/itest/testdata/test1.png
similarity index 100%
rename from testdata/test1.png
rename to itest/testdata/test1.png
diff --git a/testdata/test2.jpg b/itest/testdata/test2.jpg
similarity index 100%
rename from testdata/test2.jpg
rename to itest/testdata/test2.jpg
diff --git a/java-driver/pom.xml b/java-driver/pom.xml
new file mode 100644
index 00000000..4e0c9ccc
--- /dev/null
+++ b/java-driver/pom.xml
@@ -0,0 +1,79 @@
+
+
+
+ cn.pandadb
+ parent
+ 0.0.2
+ ../
+
+ 4.0.0
+
+ cn.pandadb
+ java-driver
+
+
+
+ cn.pandadb
+ blob-commons
+ ${pandadb.version}
+ compile
+
+
+ cn.pandadb
+ network-commons
+ ${pandadb.version}
+ compile
+
+
+ org.neo4j.driver
+ neo4j-java-driver
+ 2.0.0-alpha03
+
+
+
+
+
+
+ net.alchim31.maven
+ scala-maven-plugin
+ 3.2.1
+
+
+ scala-compile-first
+ process-resources
+
+ add-source
+ compile
+
+
+
+
+
+ maven-assembly-plugin
+
+
+
+
+
+
+
+
+ jar-with-dependencies
+
+
+
+
+ make-assembly
+ package
+
+ single
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/graiph-driver/java/org/neo4j/driver/GraphDatabase.java b/java-driver/src/main/java/org/neo4j/driver/GraphDatabase.java
similarity index 87%
rename from src/graiph-driver/java/org/neo4j/driver/GraphDatabase.java
rename to java-driver/src/main/java/org/neo4j/driver/GraphDatabase.java
index 07b6207c..dd17407b 100644
--- a/src/graiph-driver/java/org/neo4j/driver/GraphDatabase.java
+++ b/java-driver/src/main/java/org/neo4j/driver/GraphDatabase.java
@@ -20,6 +20,7 @@
import java.net.URI;
+import cn.pandadb.driver.PandaDriver;
import org.neo4j.driver.internal.DriverFactory;
import org.neo4j.driver.internal.cluster.RoutingSettings;
import org.neo4j.driver.internal.retry.RetrySettings;
@@ -116,6 +117,12 @@ public static Driver driver( URI uri, AuthToken authToken )
*/
public static Driver driver( String uri, AuthToken authToken, Config config )
{
+ //NOTE: pandadb
+ if(uri.startsWith("panda://")) {
+ return PandaDriver.create(uri, authToken, config);
+ }
+ //NOTE
+
return driver( URI.create( uri ), authToken, config );
}
@@ -154,19 +161,40 @@ public static Driver routingDriver( Iterable routingUris, AuthToken authTok
for ( URI uri : routingUris )
{
+ final Driver driver = driver( uri, authToken, config );
try
{
- return driver( uri, authToken, config );
+ driver.verifyConnectivity();
+ return driver;
}
catch ( ServiceUnavailableException e )
{
log.warn( "Unable to create routing driver for URI: " + uri, e );
+ closeDriver( driver, uri, log );
+ }
+ catch ( Throwable e )
+ {
+ // for any other errors, we first close the driver and then rethrow the original error out.
+ closeDriver( driver, uri, log );
+ throw e;
}
}
throw new ServiceUnavailableException( "Failed to discover an available server" );
}
+ private static void closeDriver( Driver driver, URI uri, Logger log )
+ {
+ try
+ {
+ driver.close();
+ }
+ catch ( Throwable closeError )
+ {
+ log.warn( "Unable to close driver towards URI: " + uri, closeError );
+ }
+ }
+
private static void assertRoutingUris( Iterable uris )
{
for ( URI uri : uris )
diff --git a/src/graiph-driver/java/org/neo4j/driver/Value.java b/java-driver/src/main/java/org/neo4j/driver/Value.java
similarity index 99%
rename from src/graiph-driver/java/org/neo4j/driver/Value.java
rename to java-driver/src/main/java/org/neo4j/driver/Value.java
index fc452404..137b9d53 100644
--- a/src/graiph-driver/java/org/neo4j/driver/Value.java
+++ b/java-driver/src/main/java/org/neo4j/driver/Value.java
@@ -44,7 +44,7 @@
import org.neo4j.driver.util.Experimental;
import java.util.function.Function;
import org.neo4j.driver.util.Immutable;
-import cn.graiph.blob.Blob;
+import cn.pandadb.blob.Blob;
/**
* A unit of data that adheres to the Neo4j type system.
diff --git a/src/graiph-driver/java/org/neo4j/driver/Values.java b/java-driver/src/main/java/org/neo4j/driver/Values.java
similarity index 99%
rename from src/graiph-driver/java/org/neo4j/driver/Values.java
rename to java-driver/src/main/java/org/neo4j/driver/Values.java
index ff379a91..2ec19853 100644
--- a/src/graiph-driver/java/org/neo4j/driver/Values.java
+++ b/java-driver/src/main/java/org/neo4j/driver/Values.java
@@ -50,7 +50,7 @@
import org.neo4j.driver.types.Point;
import org.neo4j.driver.types.Relationship;
import org.neo4j.driver.types.TypeSystem;
-import cn.graiph.blob.Blob;
+import cn.pandadb.blob.Blob;
import java.util.function.Function;
diff --git a/src/graiph-driver/java/org/neo4j/driver/internal/async/inbound/ByteBufInput.java b/java-driver/src/main/java/org/neo4j/driver/internal/async/inbound/ByteBufInput.java
similarity index 96%
rename from src/graiph-driver/java/org/neo4j/driver/internal/async/inbound/ByteBufInput.java
rename to java-driver/src/main/java/org/neo4j/driver/internal/async/inbound/ByteBufInput.java
index 5cab52fe..52de5e25 100644
--- a/src/graiph-driver/java/org/neo4j/driver/internal/async/inbound/ByteBufInput.java
+++ b/java-driver/src/main/java/org/neo4j/driver/internal/async/inbound/ByteBufInput.java
@@ -18,7 +18,7 @@
*/
package org.neo4j.driver.internal.async.inbound;
-import io.netty.buffer.ByteBuf;
+import org.neo4j.driver.internal.shaded.io.netty.buffer.ByteBuf;
import org.neo4j.driver.internal.packstream.PackInput;
diff --git a/src/graiph-driver/java/org/neo4j/driver/internal/async/inbound/InboundMessageHandler.java b/java-driver/src/main/java/org/neo4j/driver/internal/async/inbound/InboundMessageHandler.java
similarity index 87%
rename from src/graiph-driver/java/org/neo4j/driver/internal/async/inbound/InboundMessageHandler.java
rename to java-driver/src/main/java/org/neo4j/driver/internal/async/inbound/InboundMessageHandler.java
index 3fbb4eff..11317699 100644
--- a/src/graiph-driver/java/org/neo4j/driver/internal/async/inbound/InboundMessageHandler.java
+++ b/java-driver/src/main/java/org/neo4j/driver/internal/async/inbound/InboundMessageHandler.java
@@ -18,17 +18,17 @@
*/
package org.neo4j.driver.internal.async.inbound;
-import io.netty.buffer.ByteBuf;
-import io.netty.channel.ChannelHandlerContext;
-import io.netty.channel.SimpleChannelInboundHandler;
-import io.netty.handler.codec.DecoderException;
+import org.neo4j.driver.internal.shaded.io.netty.buffer.ByteBuf;
+import org.neo4j.driver.internal.shaded.io.netty.channel.ChannelHandlerContext;
+import org.neo4j.driver.internal.shaded.io.netty.channel.SimpleChannelInboundHandler;
+import org.neo4j.driver.internal.shaded.io.netty.handler.codec.DecoderException;
import org.neo4j.driver.internal.logging.ChannelActivityLogger;
import org.neo4j.driver.internal.messaging.MessageFormat;
import org.neo4j.driver.Logger;
import org.neo4j.driver.Logging;
-import static io.netty.buffer.ByteBufUtil.hexDump;
+import static org.neo4j.driver.internal.shaded.io.netty.buffer.ByteBufUtil.hexDump;
import static java.util.Objects.requireNonNull;
import static org.neo4j.driver.internal.async.connection.ChannelAttributes.messageDispatcher;
diff --git a/src/graiph-driver/java/org/neo4j/driver/internal/messaging/v1/ValueUnpackerV1.java b/java-driver/src/main/java/org/neo4j/driver/internal/messaging/v1/ValueUnpackerV1.java
similarity index 100%
rename from src/graiph-driver/java/org/neo4j/driver/internal/messaging/v1/ValueUnpackerV1.java
rename to java-driver/src/main/java/org/neo4j/driver/internal/messaging/v1/ValueUnpackerV1.java
diff --git a/src/graiph-driver/java/org/neo4j/driver/internal/messaging/v2/ValuePackerV2.java b/java-driver/src/main/java/org/neo4j/driver/internal/messaging/v2/ValuePackerV2.java
similarity index 84%
rename from src/graiph-driver/java/org/neo4j/driver/internal/messaging/v2/ValuePackerV2.java
rename to java-driver/src/main/java/org/neo4j/driver/internal/messaging/v2/ValuePackerV2.java
index 6b8c9ae5..6d7500ae 100644
--- a/src/graiph-driver/java/org/neo4j/driver/internal/messaging/v2/ValuePackerV2.java
+++ b/java-driver/src/main/java/org/neo4j/driver/internal/messaging/v2/ValuePackerV2.java
@@ -32,6 +32,7 @@
import org.neo4j.driver.internal.messaging.v1.ValuePackerV1;
import org.neo4j.driver.internal.packstream.PackOutput;
import org.neo4j.driver.internal.types.TypeConstructor;
+import org.neo4j.driver.internal.util.BoltClientBlobIO;
import org.neo4j.driver.internal.value.InternalValue;
import org.neo4j.driver.types.IsoDuration;
import org.neo4j.driver.types.Point;
@@ -66,6 +67,14 @@ public ValuePackerV2( PackOutput output )
protected void packInternalValue( InternalValue value ) throws IOException
{
TypeConstructor typeConstructor = value.typeConstructor();
+
+ //NOTE: blob
+ if (TypeConstructor.BLOB == typeConstructor) {
+ BoltClientBlobIO.packBlob(value.asBlob(), packer);
+ return;
+ }
+ //NOTE
+
switch ( typeConstructor )
{
case DATE:
@@ -96,7 +105,7 @@ protected void packInternalValue( InternalValue value ) throws IOException
private void packDate( LocalDate localDate ) throws IOException
{
- packer.packStructHeader( DATE_STRUCT_SIZE, DATE );
+ packer.packStructHeader( MessageFormatV2.DATE_STRUCT_SIZE, MessageFormatV2.DATE );
packer.pack( localDate.toEpochDay() );
}
@@ -105,14 +114,14 @@ private void packTime( OffsetTime offsetTime ) throws IOException
long nanoOfDayLocal = offsetTime.toLocalTime().toNanoOfDay();
int offsetSeconds = offsetTime.getOffset().getTotalSeconds();
- packer.packStructHeader( TIME_STRUCT_SIZE, TIME );
+ packer.packStructHeader( MessageFormatV2.TIME_STRUCT_SIZE, MessageFormatV2.TIME );
packer.pack( nanoOfDayLocal );
packer.pack( offsetSeconds );
}
private void packLocalTime( LocalTime localTime ) throws IOException
{
- packer.packStructHeader( LOCAL_TIME_STRUCT_SIZE, LOCAL_TIME );
+ packer.packStructHeader( MessageFormatV2.LOCAL_TIME_STRUCT_SIZE, MessageFormatV2.LOCAL_TIME );
packer.pack( localTime.toNanoOfDay() );
}
@@ -121,7 +130,7 @@ private void packLocalDateTime( LocalDateTime localDateTime ) throws IOException
long epochSecondUtc = localDateTime.toEpochSecond( UTC );
int nano = localDateTime.getNano();
- packer.packStructHeader( LOCAL_DATE_TIME_STRUCT_SIZE, LOCAL_DATE_TIME );
+ packer.packStructHeader( MessageFormatV2.LOCAL_DATE_TIME_STRUCT_SIZE, MessageFormatV2.LOCAL_DATE_TIME );
packer.pack( epochSecondUtc );
packer.pack( nano );
}
@@ -136,7 +145,7 @@ private void packZonedDateTime( ZonedDateTime zonedDateTime ) throws IOException
{
int offsetSeconds = ((ZoneOffset) zone).getTotalSeconds();
- packer.packStructHeader( DATE_TIME_STRUCT_SIZE, DATE_TIME_WITH_ZONE_OFFSET );
+ packer.packStructHeader( MessageFormatV2.DATE_TIME_STRUCT_SIZE, MessageFormatV2.DATE_TIME_WITH_ZONE_OFFSET );
packer.pack( epochSecondLocal );
packer.pack( nano );
packer.pack( offsetSeconds );
@@ -145,7 +154,7 @@ private void packZonedDateTime( ZonedDateTime zonedDateTime ) throws IOException
{
String zoneId = zone.getId();
- packer.packStructHeader( DATE_TIME_STRUCT_SIZE, DATE_TIME_WITH_ZONE_ID );
+ packer.packStructHeader( MessageFormatV2.DATE_TIME_STRUCT_SIZE, MessageFormatV2.DATE_TIME_WITH_ZONE_ID );
packer.pack( epochSecondLocal );
packer.pack( nano );
packer.pack( zoneId );
@@ -154,7 +163,7 @@ private void packZonedDateTime( ZonedDateTime zonedDateTime ) throws IOException
private void packDuration( IsoDuration duration ) throws IOException
{
- packer.packStructHeader( DURATION_TIME_STRUCT_SIZE, DURATION );
+ packer.packStructHeader( MessageFormatV2.DURATION_TIME_STRUCT_SIZE, MessageFormatV2.DURATION );
packer.pack( duration.months() );
packer.pack( duration.days() );
packer.pack( duration.seconds() );
@@ -179,7 +188,7 @@ else if ( point instanceof InternalPoint3D )
private void packPoint2D( Point point ) throws IOException
{
- packer.packStructHeader( POINT_2D_STRUCT_SIZE, POINT_2D_STRUCT_TYPE );
+ packer.packStructHeader( MessageFormatV2.POINT_2D_STRUCT_SIZE, MessageFormatV2.POINT_2D_STRUCT_TYPE );
packer.pack( point.srid() );
packer.pack( point.x() );
packer.pack( point.y() );
@@ -187,7 +196,7 @@ private void packPoint2D( Point point ) throws IOException
private void packPoint3D( Point point ) throws IOException
{
- packer.packStructHeader( POINT_3D_STRUCT_SIZE, POINT_3D_STRUCT_TYPE );
+ packer.packStructHeader( MessageFormatV2.POINT_3D_STRUCT_SIZE, MessageFormatV2.POINT_3D_STRUCT_TYPE );
packer.pack( point.srid() );
packer.pack( point.x() );
packer.pack( point.y() );
diff --git a/java-driver/src/main/java/org/neo4j/driver/internal/messaging/v2/ValueUnpackerV2.java b/java-driver/src/main/java/org/neo4j/driver/internal/messaging/v2/ValueUnpackerV2.java
new file mode 100644
index 00000000..82e57ad4
--- /dev/null
+++ b/java-driver/src/main/java/org/neo4j/driver/internal/messaging/v2/ValueUnpackerV2.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2002-2019 "Neo4j,"
+ * Neo4j Sweden AB [http://neo4j.com]
+ *
+ * This file is part of Neo4j.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.neo4j.driver.internal.messaging.v2;
+
+import org.neo4j.driver.Value;
+import org.neo4j.driver.internal.messaging.v1.ValueUnpackerV1;
+import org.neo4j.driver.internal.packstream.PackInput;
+import org.neo4j.driver.internal.types.TypeConstructor;
+import org.neo4j.driver.internal.util.BoltClientBlobIO;
+
+import java.io.IOException;
+import java.time.*;
+
+import static java.time.ZoneOffset.UTC;
+import static org.neo4j.driver.Values.*;
+import static org.neo4j.driver.internal.messaging.v2.MessageFormatV2.*;
+
+public class ValueUnpackerV2 extends ValueUnpackerV1 {
+ public ValueUnpackerV2(PackInput input) {
+ super(input);
+ }
+
+ //NOTE: blob support
+ protected Value unpack() throws IOException {
+ Value blobValue = BoltClientBlobIO.unpackBlob(unpacker);
+ if (blobValue != null)
+ return blobValue;
+
+ return super.unpack();
+ }
+ //NOTE
+
+ @Override
+ protected Value unpackStruct(long size, byte type) throws IOException {
+ switch (type) {
+ case DATE:
+ ensureCorrectStructSize(TypeConstructor.DATE, DATE_STRUCT_SIZE, size);
+ return unpackDate();
+ case TIME:
+ ensureCorrectStructSize(TypeConstructor.TIME, TIME_STRUCT_SIZE, size);
+ return unpackTime();
+ case LOCAL_TIME:
+ ensureCorrectStructSize(TypeConstructor.LOCAL_TIME, LOCAL_TIME_STRUCT_SIZE, size);
+ return unpackLocalTime();
+ case LOCAL_DATE_TIME:
+ ensureCorrectStructSize(TypeConstructor.LOCAL_DATE_TIME, LOCAL_DATE_TIME_STRUCT_SIZE, size);
+ return unpackLocalDateTime();
+ case DATE_TIME_WITH_ZONE_OFFSET:
+ ensureCorrectStructSize(TypeConstructor.DATE_TIME, DATE_TIME_STRUCT_SIZE, size);
+ return unpackDateTimeWithZoneOffset();
+ case DATE_TIME_WITH_ZONE_ID:
+ ensureCorrectStructSize(TypeConstructor.DATE_TIME, DATE_TIME_STRUCT_SIZE, size);
+ return unpackDateTimeWithZoneId();
+ case DURATION:
+ ensureCorrectStructSize(TypeConstructor.DURATION, DURATION_TIME_STRUCT_SIZE, size);
+ return unpackDuration();
+ case POINT_2D_STRUCT_TYPE:
+ ensureCorrectStructSize(TypeConstructor.POINT, POINT_2D_STRUCT_SIZE, size);
+ return unpackPoint2D();
+ case POINT_3D_STRUCT_TYPE:
+ ensureCorrectStructSize(TypeConstructor.POINT, POINT_3D_STRUCT_SIZE, size);
+ return unpackPoint3D();
+ default:
+ return super.unpackStruct(size, type);
+ }
+ }
+
+ private Value unpackDate() throws IOException {
+ long epochDay = unpacker.unpackLong();
+ return value(LocalDate.ofEpochDay(epochDay));
+ }
+
+ private Value unpackTime() throws IOException {
+ long nanoOfDayLocal = unpacker.unpackLong();
+ int offsetSeconds = Math.toIntExact(unpacker.unpackLong());
+
+ LocalTime localTime = LocalTime.ofNanoOfDay(nanoOfDayLocal);
+ ZoneOffset offset = ZoneOffset.ofTotalSeconds(offsetSeconds);
+ return value(OffsetTime.of(localTime, offset));
+ }
+
+ private Value unpackLocalTime() throws IOException {
+ long nanoOfDayLocal = unpacker.unpackLong();
+ return value(LocalTime.ofNanoOfDay(nanoOfDayLocal));
+ }
+
+ private Value unpackLocalDateTime() throws IOException {
+ long epochSecondUtc = unpacker.unpackLong();
+ int nano = Math.toIntExact(unpacker.unpackLong());
+ return value(LocalDateTime.ofEpochSecond(epochSecondUtc, nano, UTC));
+ }
+
+ private Value unpackDateTimeWithZoneOffset() throws IOException {
+ long epochSecondLocal = unpacker.unpackLong();
+ int nano = Math.toIntExact(unpacker.unpackLong());
+ int offsetSeconds = Math.toIntExact(unpacker.unpackLong());
+ return value(newZonedDateTime(epochSecondLocal, nano, ZoneOffset.ofTotalSeconds(offsetSeconds)));
+ }
+
+ private Value unpackDateTimeWithZoneId() throws IOException {
+ long epochSecondLocal = unpacker.unpackLong();
+ int nano = Math.toIntExact(unpacker.unpackLong());
+ String zoneIdString = unpacker.unpackString();
+ return value(newZonedDateTime(epochSecondLocal, nano, ZoneId.of(zoneIdString)));
+ }
+
+ private Value unpackDuration() throws IOException {
+ long months = unpacker.unpackLong();
+ long days = unpacker.unpackLong();
+ long seconds = unpacker.unpackLong();
+ int nanoseconds = Math.toIntExact(unpacker.unpackLong());
+ return isoDuration(months, days, seconds, nanoseconds);
+ }
+
+ private Value unpackPoint2D() throws IOException {
+ int srid = Math.toIntExact(unpacker.unpackLong());
+ double x = unpacker.unpackDouble();
+ double y = unpacker.unpackDouble();
+ return point(srid, x, y);
+ }
+
+ private Value unpackPoint3D() throws IOException {
+ int srid = Math.toIntExact(unpacker.unpackLong());
+ double x = unpacker.unpackDouble();
+ double y = unpacker.unpackDouble();
+ double z = unpacker.unpackDouble();
+ return point(srid, x, y, z);
+ }
+
+ private static ZonedDateTime newZonedDateTime(long epochSecondLocal, long nano, ZoneId zoneId) {
+ Instant instant = Instant.ofEpochSecond(epochSecondLocal, nano);
+ LocalDateTime localDateTime = LocalDateTime.ofInstant(instant, UTC);
+ return ZonedDateTime.of(localDateTime, zoneId);
+ }
+}
diff --git a/src/graiph-driver/java/org/neo4j/driver/internal/messaging/v3/MessageWriterV3.java b/java-driver/src/main/java/org/neo4j/driver/internal/messaging/v3/MessageWriterV3.java
similarity index 92%
rename from src/graiph-driver/java/org/neo4j/driver/internal/messaging/v3/MessageWriterV3.java
rename to java-driver/src/main/java/org/neo4j/driver/internal/messaging/v3/MessageWriterV3.java
index 2706d5ad..1a45408d 100644
--- a/src/graiph-driver/java/org/neo4j/driver/internal/messaging/v3/MessageWriterV3.java
+++ b/java-driver/src/main/java/org/neo4j/driver/internal/messaging/v3/MessageWriterV3.java
@@ -20,6 +20,8 @@
import java.util.Map;
+import cn.pandadb.blob.BlobMessageSignature;
+import org.neo4j.driver.internal.GetBlobMessageEncoder;
import org.neo4j.driver.internal.messaging.AbstractMessageWriter;
import org.neo4j.driver.internal.messaging.MessageEncoder;
import org.neo4j.driver.internal.messaging.encode.BeginMessageEncoder;
@@ -61,6 +63,10 @@ private static Map buildEncoders()
result.put( DiscardAllMessage.SIGNATURE, new DiscardAllMessageEncoder() );
result.put( PullAllMessage.SIGNATURE, new PullAllMessageEncoder() );
+ //NOTE: GetBlobMessageEncoder
+ result.put(BlobMessageSignature.SIGNATURE_GET_BLOB(), new GetBlobMessageEncoder() ); // new
+ //NOTE
+
result.put( BeginMessage.SIGNATURE, new BeginMessageEncoder() );
result.put( CommitMessage.SIGNATURE, new CommitMessageEncoder() );
result.put( RollbackMessage.SIGNATURE, new RollbackMessageEncoder() );
diff --git a/src/graiph-driver/java/org/neo4j/driver/internal/messaging/v4/MessageWriterV4.java b/java-driver/src/main/java/org/neo4j/driver/internal/messaging/v4/MessageWriterV4.java
similarity index 92%
rename from src/graiph-driver/java/org/neo4j/driver/internal/messaging/v4/MessageWriterV4.java
rename to java-driver/src/main/java/org/neo4j/driver/internal/messaging/v4/MessageWriterV4.java
index 28d6e99f..940396b3 100644
--- a/src/graiph-driver/java/org/neo4j/driver/internal/messaging/v4/MessageWriterV4.java
+++ b/java-driver/src/main/java/org/neo4j/driver/internal/messaging/v4/MessageWriterV4.java
@@ -20,6 +20,8 @@
import java.util.Map;
+import cn.pandadb.blob.BlobMessageSignature;
+import org.neo4j.driver.internal.GetBlobMessageEncoder;
import org.neo4j.driver.internal.messaging.AbstractMessageWriter;
import org.neo4j.driver.internal.messaging.MessageEncoder;
import org.neo4j.driver.internal.messaging.encode.BeginMessageEncoder;
@@ -61,6 +63,10 @@ private static Map buildEncoders()
result.put( DiscardMessage.SIGNATURE, new DiscardMessageEncoder() ); // new
result.put( PullMessage.SIGNATURE, new PullMessageEncoder() ); // new
+ //NOTE: GetBlobMessageEncoder
+ result.put(BlobMessageSignature.SIGNATURE_GET_BLOB(), new GetBlobMessageEncoder() ); // new
+ //NOTE
+
result.put( BeginMessage.SIGNATURE, new BeginMessageEncoder() );
result.put( CommitMessage.SIGNATURE, new CommitMessageEncoder() );
result.put( RollbackMessage.SIGNATURE, new RollbackMessageEncoder() );
diff --git a/src/graiph-driver/java/org/neo4j/driver/internal/types/TypeConstructor.java b/java-driver/src/main/java/org/neo4j/driver/internal/types/TypeConstructor.java
similarity index 100%
rename from src/graiph-driver/java/org/neo4j/driver/internal/types/TypeConstructor.java
rename to java-driver/src/main/java/org/neo4j/driver/internal/types/TypeConstructor.java
diff --git a/src/graiph-driver/java/org/neo4j/driver/internal/value/ValueAdapter.java b/java-driver/src/main/java/org/neo4j/driver/internal/value/ValueAdapter.java
similarity index 99%
rename from src/graiph-driver/java/org/neo4j/driver/internal/value/ValueAdapter.java
rename to java-driver/src/main/java/org/neo4j/driver/internal/value/ValueAdapter.java
index a52a7e0b..3d393d9e 100644
--- a/src/graiph-driver/java/org/neo4j/driver/internal/value/ValueAdapter.java
+++ b/java-driver/src/main/java/org/neo4j/driver/internal/value/ValueAdapter.java
@@ -41,7 +41,7 @@
import org.neo4j.driver.types.Point;
import org.neo4j.driver.types.Relationship;
import org.neo4j.driver.types.Type;
-import cn.graiph.blob.Blob;
+import cn.pandadb.blob.Blob;
import java.util.function.Function;
diff --git a/java-driver/src/main/scala/cn/pandadb/driver/PandaDriver.scala b/java-driver/src/main/scala/cn/pandadb/driver/PandaDriver.scala
new file mode 100644
index 00000000..1bef857d
--- /dev/null
+++ b/java-driver/src/main/scala/cn/pandadb/driver/PandaDriver.scala
@@ -0,0 +1,216 @@
+package cn.pandadb.driver
+
+import java.io.IOException
+import java.net.URI
+import java.security.GeneralSecurityException
+import java.util.Collections
+import java.{security, util}
+import java.util.concurrent.{CompletableFuture, CompletionStage}
+
+import cn.pandadb.cypherplus.utils.CypherPlusUtils
+import cn.pandadb.network.{ClusterClient, NodeAddress, ZookeeperBasedClusterClient}
+import org.apache.commons.lang3.NotImplementedException
+import org.neo4j.driver.Config.TrustStrategy
+import org.neo4j.driver.{Transaction, Value, _}
+import org.neo4j.driver.async.{AsyncSession, AsyncStatementRunner, AsyncTransaction, AsyncTransactionWork, StatementResultCursor}
+import org.neo4j.driver.exceptions.ClientException
+import org.neo4j.driver.internal.async.connection.BootstrapFactory
+import org.neo4j.driver.internal.cluster.{RoutingContext, RoutingSettings}
+import org.neo4j.driver.internal.{AbstractStatementRunner, BoltServerAddress, DirectConnectionProvider, DriverFactory, SessionConfig, SessionFactory, SessionFactoryImpl}
+import org.neo4j.driver.internal.metrics.{InternalMetricsProvider, MetricsProvider}
+import org.neo4j.driver.internal.retry.{ExponentialBackoffRetryLogic, RetryLogic, RetrySettings}
+import org.neo4j.driver.internal.security.SecurityPlan
+import org.neo4j.driver.internal.spi.ConnectionProvider
+import org.neo4j.driver.internal.types.InternalTypeSystem
+import org.neo4j.driver.internal.util.{Clock, Extract, Futures}
+import org.neo4j.driver.internal.value.MapValue
+import org.neo4j.driver.reactive.{RxSession, RxStatementResult, RxTransaction, RxTransactionWork}
+import org.neo4j.driver.types.TypeSystem
+import org.reactivestreams.Publisher
+
+import scala.collection.JavaConversions
+import cn.pandadb.driver.PandaTransaction
+
+import scala.util.matching.Regex
+
+/**
+ * Created by bluejoe on 2019/11/21.
+ */
+object PandaDriver {
+ def create(uri: String, authToken: AuthToken, config: Config): Driver = {
+ new PandaDriver(uri, authToken, config)
+ }
+}
+
+class PandaDriver(uri: String, authToken: AuthToken, config: Config) extends Driver {
+ val clusterClient: ClusterClient = createClusterClient(uri);
+ // val defaultSessionConfig = new SessionConfig()
+ val defaultSessionConfig = SessionConfig.empty()
+ override def closeAsync(): CompletionStage[Void] = {
+ //TODO
+ new CompletableFuture[Void]();
+ }
+
+ override def session(): Session = session(defaultSessionConfig)
+
+ override def session(sessionConfig: SessionConfig): Session = new PandaSession(sessionConfig, clusterClient);
+
+ override def defaultTypeSystem(): TypeSystem = InternalTypeSystem.TYPE_SYSTEM
+
+ override def rxSession(): RxSession = {
+ this.rxSession(defaultSessionConfig)
+ }
+
+ override def rxSession(sessionConfig: SessionConfig): RxSession = {
+ throw new NotImplementedException("rxSession")
+ }
+
+ /**
+ * verifyConnectivityAsync and verifyConnectivity is not right , because uri is zkString
+ */
+
+ override def verifyConnectivityAsync(): CompletionStage[Void] = {
+ throw new NotImplementedException("verifyConnectivityAsync")
+ }
+
+ override def verifyConnectivity(): Unit = {
+ Futures.blockingGet(this.verifyConnectivityAsync())
+ }
+
+ override def metrics(): Metrics = {
+ createDriverMetrics(config, this.createClock()).metrics()
+ }
+ private def createDriverMetrics(config: Config, clock: Clock ): MetricsProvider = {
+ if (config.isMetricsEnabled()) new InternalMetricsProvider(clock) else MetricsProvider.METRICS_DISABLED_PROVIDER
+ }
+ private def createClock(): Clock = {
+ Clock.SYSTEM
+ }
+
+ override def asyncSession(): AsyncSession = {
+ this.asyncSession(defaultSessionConfig)
+ }
+
+ override def asyncSession(sessionConfig: SessionConfig): AsyncSession = {
+ throw new NotImplementedException("asyncSession")
+ }
+
+ override def close(): Unit = {
+
+ }
+
+ //wait to finish
+ override def isEncrypted: Boolean = {
+ throw new NotImplementedException("isEncrypted")
+ }
+
+ private def createClusterClient(uri: String): ClusterClient = {
+ //scalastyle:off
+ val pattern = new Regex("((2[0-4]\\d|25[0-5]|[01]?\\d\\d?)\\.){3}(2[0-4]\\d|25[0-5]|[01]?\\d\\d?):[0-9]{1,5}")
+ val zkString = (pattern findAllIn uri).mkString(",")
+ new ZookeeperBasedClusterClient(zkString)
+ }
+}
+
+class PandaSession(sessionConfig: SessionConfig, clusterOperator: ClusterClient) extends Session {
+
+ var session: Session = null
+ var readDriver: Driver = null
+ var writeDriver : Driver = null
+ private def getSession(isWriteStatement: Boolean): Session = {
+ if (!(this.session==null)) this.session.close()
+ if (isWriteStatement) {
+ if (this.writeDriver==null) this.writeDriver = SelectNode.getDriver(isWriteStatement, clusterOperator)
+ this.session = this.writeDriver.session(sessionConfig)
+ } else {
+ if (this.readDriver==null) this.readDriver = SelectNode.getDriver(isWriteStatement, clusterOperator)
+ this.session = this.readDriver.session(sessionConfig)
+ }
+ this.session
+ }
+
+ override def writeTransaction[T](work: TransactionWork[T]): T = {
+ this.writeTransaction(work, TransactionConfig.empty())
+ }
+
+
+ override def writeTransaction[T](work: TransactionWork[T], config: TransactionConfig): T = {
+ getSession(true).writeTransaction(work, config)
+ }
+
+
+ override def readTransaction[T](work: TransactionWork[T]): T = {
+ this.readTransaction(work, TransactionConfig.empty())
+ }
+
+ override def readTransaction[T](work: TransactionWork[T], config: TransactionConfig): T = {
+ getSession(false).readTransaction(work, config)
+ }
+
+ override def run(statement: String, config: TransactionConfig): StatementResult = {
+ this.run(statement, Collections.emptyMap(), config)
+ }
+
+ override def run(statement: String, parameters: util.Map[String, AnyRef], config: TransactionConfig): StatementResult = {
+ this.run(new Statement(statement, parameters), config)
+ }
+
+ override def run(statement: Statement, config: TransactionConfig): StatementResult = {
+ val tempState = statement.text().toLowerCase()
+ val isWriteStatement = CypherPlusUtils.isWriteStatement(tempState)
+ getSession(isWriteStatement)
+ this.session.run(statement, config)
+ }
+
+ override def close(): Unit = {
+ if (!(this.session == null)) session.close()
+ if (!(this.writeDriver == null)) this.writeDriver.close()
+ if (!(this.readDriver == null)) this.readDriver.close()
+ }
+
+ override def lastBookmark(): String = {
+ session.lastBookmark()
+ }
+
+ override def reset(): Unit = {
+ session.reset()
+ }
+
+ override def beginTransaction(): Transaction = {
+ this.beginTransaction(TransactionConfig.empty())
+ }
+
+ override def beginTransaction(config: TransactionConfig): Transaction = {
+ /*isTransaction = true
+ this.config = config
+ this.transaction*/
+ new PandaTransaction(sessionConfig, config, clusterOperator)
+ }
+
+ override def run(statementTemplate: String, parameters: Value): StatementResult = {
+ this.run(new Statement(statementTemplate, parameters))
+ }
+
+ override def run(statementTemplate: String, statementParameters: util.Map[String, AnyRef]): StatementResult = {
+ this.run(statementTemplate, AbstractStatementRunner.parameters(statementParameters))
+ }
+
+ override def run(statementTemplate: String, statementParameters: Record): StatementResult = {
+ //session.run(statementTemplate, statementParameters) AbstractStatementRunner
+ //this.run(statementTemplate, parameters(statementParameters))
+ this.run(statementTemplate, AbstractStatementRunner.parameters(statementParameters))
+ }
+
+ override def run(statementTemplate: String): StatementResult = {
+ this.run(statementTemplate, Values.EmptyMap)
+ }
+
+ override def run(statement: Statement): StatementResult = {
+ //session.run(statement)
+ this.run(statement, TransactionConfig.empty())
+ }
+
+ override def isOpen: Boolean = {
+ session.isOpen
+ }
+}
\ No newline at end of file
diff --git a/java-driver/src/main/scala/cn/pandadb/driver/PandaTransaction.scala b/java-driver/src/main/scala/cn/pandadb/driver/PandaTransaction.scala
new file mode 100644
index 00000000..6dde86e3
--- /dev/null
+++ b/java-driver/src/main/scala/cn/pandadb/driver/PandaTransaction.scala
@@ -0,0 +1,96 @@
+package cn.pandadb.driver
+
+import java.util
+
+import cn.pandadb.cypherplus.utils.CypherPlusUtils
+import cn.pandadb.network.{ClusterClient, NodeAddress}
+import org.neo4j.driver.internal.{AbstractStatementRunner, SessionConfig}
+import org.neo4j.driver.{AuthTokens, Driver, GraphDatabase, Record, Session, Statement, StatementResult, StatementRunner, Transaction, TransactionConfig, Value, Values}
+import scala.collection.mutable.ArrayBuffer
+
+/**
+ * @Author: codeBabyLin
+ * @Description:
+ * @Date: Created in 9:06 2019/11/26
+ * @Modified By:
+ */
+
+class PandaTransaction(sessionConfig: SessionConfig, config: TransactionConfig, clusterOperator: ClusterClient) extends Transaction{
+
+ var transactionArray: ArrayBuffer[Transaction] = ArrayBuffer[Transaction]() //save session
+ var sessionArray: ArrayBuffer[Session] = ArrayBuffer[Session]() // save transaction
+
+ var transaction: Transaction = _
+ var writeTransaction: Transaction = _
+ var session: Session = _
+ var readDriver: Driver = _
+ var writeDriver : Driver = _
+ //rule1 one session ,one transaction
+ //rule2 session closed,transaction does't work
+ private def getSession(isWriteStatement: Boolean): Session = {
+ //if (!(this.session==null)) this.session.close() //session the sanme with the Transaction can not close
+ if (isWriteStatement) {
+ if (this.writeDriver==null) this.writeDriver = SelectNode.getDriver(isWriteStatement, clusterOperator)
+ this.session = this.writeDriver.session(sessionConfig)
+ } else {
+ if (this.readDriver==null) this.readDriver = SelectNode.getDriver(isWriteStatement, clusterOperator)
+ this.session = this.readDriver.session(sessionConfig)
+ }
+ this.session
+ }
+ private def getTransactionReady(isWriteStatement: Boolean): Transaction = {
+ if (!(this.writeTransaction==null)) this.transaction = this.writeTransaction //reuse the wrtie transaction
+ else {
+ this.session = getSession(isWriteStatement)
+ this.transaction = session.beginTransaction(config)
+ if(isWriteStatement) this.writeTransaction = this.transaction
+ this.sessionArray += this.session
+ this.transactionArray += this.transaction
+ }
+ this.transaction
+
+ }
+
+ override def success(): Unit = {
+ if (this.transactionArray.nonEmpty) this.transactionArray.foreach(trans => trans.success())
+ }
+
+ override def failure(): Unit = {
+ if (this.transactionArray.nonEmpty) this.transactionArray.foreach(trans => trans.failure())
+ }
+
+ override def close(): Unit = {
+ if (this.transactionArray.nonEmpty) this.transactionArray.foreach(trans => trans.close())
+ if (this.sessionArray.nonEmpty) this.sessionArray.foreach(sess => sess.close())
+ if (!(this.writeDriver == null)) this.writeDriver.close()
+ if (!(this.readDriver == null)) this.readDriver.close()
+ }
+
+ override def run(s: String, value: Value): StatementResult = {
+ this.run(new Statement(s, value))
+ }
+
+ override def run(s: String, map: util.Map[String, AnyRef]): StatementResult = {
+ this.run(s, AbstractStatementRunner.parameters(map))
+ }
+
+ override def run(s: String, record: Record): StatementResult = {
+ this.run(s, AbstractStatementRunner.parameters(record))
+ }
+
+ override def run(s: String): StatementResult = {
+ this.run(s, Values.EmptyMap)
+ }
+
+ override def run(statement: Statement): StatementResult = {
+ //transanction could not be closed until close function
+ val tempState = statement.text().toLowerCase()
+ val isWriteStatement = CypherPlusUtils.isWriteStatement(tempState)
+ getTransactionReady(isWriteStatement)
+ this.transaction.run(statement)
+ }
+
+ override def isOpen: Boolean = {
+ if (!(this.transaction == null)) this.transaction.isOpen else true
+ }
+}
diff --git a/java-driver/src/main/scala/cn/pandadb/driver/SelectNode.scala b/java-driver/src/main/scala/cn/pandadb/driver/SelectNode.scala
new file mode 100644
index 00000000..44ef806f
--- /dev/null
+++ b/java-driver/src/main/scala/cn/pandadb/driver/SelectNode.scala
@@ -0,0 +1,131 @@
+package cn.pandadb.driver
+
+import cn.pandadb.network.{ClusterClient, NodeAddress}
+import org.neo4j.driver.{AuthTokens, Driver, GraphDatabase}
+
+import scala.collection.mutable
+
+/**
+ * @Author: codeBabyLin
+ * @Description:
+ * @Date: Created at 20:50 2019/11/27
+ * @Modified By:
+ */
+trait Strategy{
+
+}
+case class RANDOM_PICK() extends Strategy{
+
+}
+case class ROBIN_ROUND() extends Strategy{
+
+}
+case class WORK_TIME_PICK() extends Strategy{
+
+}
+case class DEFAULT_PICK() extends Strategy{
+
+}
+
+case class EASY_ROUND() extends Strategy{
+
+}
+
+object SelectNode {
+
+ val RONDOM_POLICY = 0
+ val _POLICY = 1
+ val robinArray = mutable.Map[NodeAddress, Long]()
+ private var policy: Strategy = null
+ private var index = 0
+
+ private def getWriteNode(clusterOperator: ClusterClient): NodeAddress = {
+ clusterOperator.getWriteMasterNode()
+ //policyDefault
+ }
+
+ private def policyRandom(clusterOperator: ClusterClient): NodeAddress = {
+
+ val nodeLists = clusterOperator.getAllNodes().toList
+ val index = (new util.Random).nextInt(nodeLists.length)
+ nodeLists(index)
+
+ }
+
+ private def policyRobinRound(clusterOperator: ClusterClient): NodeAddress = {
+ if (robinArray.size == 0) {
+ clusterOperator.getAllNodes().foreach(node => robinArray += node -> 0)
+ }
+ val list = robinArray.toList
+ val sorted = list.sortBy(node => node._2)
+ val head = sorted.head
+ val node = robinArray.toList.sortBy(u => u._2).head._1
+ robinArray(node) += 1
+ node
+ }
+
+ private def easyRound(clusterOperator: ClusterClient): NodeAddress = {
+ val nodeLists = clusterOperator.getAllNodes().toList
+ if (this.index>=nodeLists.length) {
+ this.index = 0
+ }
+ val node = nodeLists(this.index)
+ this.index += 1
+ node
+
+ }
+
+ def testRobinRound(): NodeAddress = {
+
+ val list = robinArray.toList
+ val sorted = list.sortBy(node => node._2)
+ val head = sorted.head._1
+ //val node = robinArray.toList.sortBy(u => u._2).head._1
+ robinArray(head) += 1
+ head
+
+ }
+
+ private def policyDefault(): NodeAddress = {
+ val hos = "10.0.86.179"
+ val por = 7687
+ new NodeAddress(hos, por)
+ }
+ private def getReadNode(clusterOperator: ClusterClient, strategy: Strategy): NodeAddress = {
+ strategy match {
+ case RANDOM_PICK() => policyRandom(clusterOperator)
+ case DEFAULT_PICK() => policyDefault
+ case ROBIN_ROUND() => policyRobinRound(clusterOperator)
+ case EASY_ROUND() => easyRound(clusterOperator)
+ case _ => policyDefault
+
+ }
+ }
+ private def getNode(isWriteStatement: Boolean, clusterOperator: ClusterClient, strategy: Strategy): NodeAddress = {
+ if (isWriteStatement) getWriteNode(clusterOperator) else getReadNode(clusterOperator, strategy)
+ }
+ def setPolicy(strategy: Strategy): Unit = {
+ this.policy = strategy
+ }
+ def getPolicy(): String = {
+ this.policy match {
+ case RANDOM_PICK() => "RANDOM_PICK"
+ case DEFAULT_PICK() => "DEFAULT_PICK"
+ case ROBIN_ROUND() => "ROBIN_ROUND"
+ case EASY_ROUND() => "EASY_ROUND"
+ case _ => "policyDefault-RANDOM_PICK"
+ }
+ }
+ def getDriver(isWriteStatement: Boolean, clusterOperator: ClusterClient): Driver = {
+ if (this.policy ==null) this.policy = new RANDOM_PICK
+ getDriver(isWriteStatement, clusterOperator, this.policy)
+ }
+ def getDriver(isWriteStatement: Boolean, clusterOperator: ClusterClient, strategy: Strategy): Driver = {
+ //val node = getNode(isWriteStatement, clusterOperator, new DEFAULT_PICK)
+ val node = getNode(isWriteStatement, clusterOperator, strategy)
+ val host = node.host
+ val port = node.port
+ val uri = s"bolt://$host:$port"
+ GraphDatabase.driver(uri, AuthTokens.basic("", ""))
+ }
+}
diff --git a/src/graiph-driver/scala/org/neo4j/driver/internal/messages.scala b/java-driver/src/main/scala/org/neo4j/driver/internal/messages.scala
similarity index 88%
rename from src/graiph-driver/scala/org/neo4j/driver/internal/messages.scala
rename to java-driver/src/main/scala/org/neo4j/driver/internal/messages.scala
index 31d356ea..e5f8127a 100644
--- a/src/graiph-driver/scala/org/neo4j/driver/internal/messages.scala
+++ b/java-driver/src/main/scala/org/neo4j/driver/internal/messages.scala
@@ -1,8 +1,8 @@
package org.neo4j.driver.internal
import java.util.concurrent.CompletableFuture
-import cn.graiph.blob.BlobMessageSignature
-import cn.graiph.util.Logging
+import cn.pandadb.blob.BlobMessageSignature
+import cn.pandadb.util.Logging
import org.neo4j.driver.Value
import org.neo4j.driver.internal.messaging.{Message, MessageEncoder, ValuePacker}
import org.neo4j.driver.internal.spi.ResponseHandler
@@ -33,7 +33,8 @@ class GetBlobMessageEncoder extends MessageEncoder {
}
}
-class GetBlobMessageHandler(report: CompletableFuture[(BlobChunk, ArrayBuffer[CompletableFuture[BlobChunk]])], exception: CompletableFuture[Throwable])
+class GetBlobMessageHandler(report: CompletableFuture[(BlobChunk, ArrayBuffer[CompletableFuture[BlobChunk]])],
+ exception: CompletableFuture[Throwable])
extends ResponseHandler with Logging {
val _chunks = ArrayBuffer[CompletableFuture[BlobChunk]]();
var _completedIndex = -1;
@@ -67,12 +68,14 @@ class GetBlobMessageHandler(report: CompletableFuture[(BlobChunk, ArrayBuffer[Co
override def onFailure(error: Throwable): Unit = {
exception.complete(error);
- if (!report.isDone)
+ if (!report.isDone) {
report.complete(null);
+ }
_chunks.foreach { x =>
- if (!x.isDone)
+ if (!x.isDone) {
x.complete(null)
+ }
}
}
}
\ No newline at end of file
diff --git a/src/graiph-driver/scala/org/neo4j/driver/internal/util/BoltClientBlobIO.scala b/java-driver/src/main/scala/org/neo4j/driver/internal/util/BoltClientBlobIO.scala
similarity index 95%
rename from src/graiph-driver/scala/org/neo4j/driver/internal/util/BoltClientBlobIO.scala
rename to java-driver/src/main/scala/org/neo4j/driver/internal/util/BoltClientBlobIO.scala
index ac7ba62f..d8a713d8 100644
--- a/src/graiph-driver/scala/org/neo4j/driver/internal/util/BoltClientBlobIO.scala
+++ b/java-driver/src/main/scala/org/neo4j/driver/internal/util/BoltClientBlobIO.scala
@@ -2,8 +2,8 @@ package org.neo4j.driver.internal.util
import java.util
-import cn.graiph.blob.{BlobIO, InlineBlob, BlobId, Blob}
-import cn.graiph.util.ReflectUtils
+import cn.pandadb.blob.{BlobIO, InlineBlob, BlobId, Blob}
+import cn.pandadb.util.ReflectUtils
import ReflectUtils._
import org.neo4j.driver.Value
import org.neo4j.driver.internal.spi.Connection
diff --git a/src/graiph-driver/scala/org/neo4j/driver/internal/value/InternalBlobValue.scala b/java-driver/src/main/scala/org/neo4j/driver/internal/value/InternalBlobValue.scala
similarity index 92%
rename from src/graiph-driver/scala/org/neo4j/driver/internal/value/InternalBlobValue.scala
rename to java-driver/src/main/scala/org/neo4j/driver/internal/value/InternalBlobValue.scala
index 178985f1..0bdec0c5 100644
--- a/src/graiph-driver/scala/org/neo4j/driver/internal/value/InternalBlobValue.scala
+++ b/java-driver/src/main/scala/org/neo4j/driver/internal/value/InternalBlobValue.scala
@@ -3,7 +3,8 @@ package org.neo4j.driver.internal.value
import java.io.{ByteArrayInputStream, IOException, InputStream}
import java.util.concurrent.CompletableFuture
-import cn.graiph.blob.{MimeType, Blob, InputStreamSource}
+import cn.pandadb.blob.{MimeType, Blob, InputStreamSource}
+import cn.pandadb.util.PandaException
import org.neo4j.driver.internal._
import org.neo4j.driver.internal.spi.Connection
import org.neo4j.driver.internal.types.{TypeConstructor, TypeRepresentation}
@@ -27,7 +28,7 @@ class InternalBlobValue(val blob: Blob) extends ValueAdapter {
override def asBlob: Blob = blob;
- override def asObject = blob;
+ override def asObject: AnyRef = blob;
override def toString: String = s"BoltBlobValue(blob=${blob.toString})"
}
@@ -108,6 +109,6 @@ class BlobInputStream(firstChunk: BlobChunk, chunkFutures: ArrayBuffer[Completab
}
}
-class FailedToReadStreamException(cause: Throwable) extends RuntimeException(cause) {
+class FailedToReadStreamException(cause: Throwable) extends PandaException(s"failed to read stream", cause) {
}
\ No newline at end of file
diff --git a/neo4j-hacking/pom.xml b/neo4j-hacking/pom.xml
new file mode 100644
index 00000000..2416dc72
--- /dev/null
+++ b/neo4j-hacking/pom.xml
@@ -0,0 +1,53 @@
+
+
+
+ parent
+ cn.pandadb
+ 0.0.2
+ ../
+
+ 4.0.0
+
+ cn.pandadb
+ neo4j-hacking
+ plugins, configuration...
+
+
+
+ com.google.code.findbugs
+ jsr305
+
+
+ cn.pandadb
+ commons
+ ${pandadb.version}
+
+
+ org.neo4j
+ neo4j
+
+
+
+
+
+
+ net.alchim31.maven
+ scala-maven-plugin
+ 3.2.1
+
+
+ scala-compile-first
+ process-resources
+
+ add-source
+ compile
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/blob/java/org/neo4j/graphdb/facade/GraphDatabaseFacadeFactory.java b/neo4j-hacking/src/main/java/org/neo4j/graphdb/facade/GraphDatabaseFacadeFactory.java
similarity index 93%
rename from src/blob/java/org/neo4j/graphdb/facade/GraphDatabaseFacadeFactory.java
rename to neo4j-hacking/src/main/java/org/neo4j/graphdb/facade/GraphDatabaseFacadeFactory.java
index d4db144b..ebe56135 100644
--- a/src/blob/java/org/neo4j/graphdb/facade/GraphDatabaseFacadeFactory.java
+++ b/neo4j-hacking/src/main/java/org/neo4j/graphdb/facade/GraphDatabaseFacadeFactory.java
@@ -19,10 +19,8 @@
*/
package org.neo4j.graphdb.facade;
-import java.io.File;
-import java.util.Map;
-import java.util.function.Function;
-
+import cn.pandadb.context.GraphDatabaseStartedEvent;
+import cn.pandadb.util.PandaEventHub;
import org.neo4j.bolt.BoltServer;
import org.neo4j.dbms.database.DatabaseManager;
import org.neo4j.graphdb.DependencyResolver;
@@ -30,7 +28,6 @@
import org.neo4j.graphdb.Path;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.factory.GraphDatabaseSettings;
-import org.neo4j.internal.DataCollectorManager;
import org.neo4j.graphdb.factory.module.DataSourceModule;
import org.neo4j.graphdb.factory.module.PlatformModule;
import org.neo4j.graphdb.factory.module.edition.AbstractEditionModule;
@@ -38,6 +35,7 @@
import org.neo4j.graphdb.spatial.Geometry;
import org.neo4j.graphdb.spatial.Point;
import org.neo4j.helpers.collection.Pair;
+import org.neo4j.internal.DataCollectorManager;
import org.neo4j.internal.kernel.api.exceptions.KernelException;
import org.neo4j.internal.kernel.api.security.SecurityContext;
import org.neo4j.kernel.api.KernelTransaction;
@@ -48,7 +46,6 @@
import org.neo4j.kernel.configuration.Config;
import org.neo4j.kernel.extension.KernelExtensionFactory;
import org.neo4j.kernel.impl.api.dbms.NonTransactionalDbmsOperations;
-import org.neo4j.kernel.impl.blob.BlobPropertyStoreService;
import org.neo4j.kernel.impl.cache.VmPauseMonitorComponent;
import org.neo4j.kernel.impl.factory.DatabaseInfo;
import org.neo4j.kernel.impl.factory.GraphDatabaseFacade;
@@ -68,15 +65,12 @@
import org.neo4j.scheduler.DeferredExecutor;
import org.neo4j.scheduler.Group;
-import static org.neo4j.internal.kernel.api.procs.Neo4jTypes.NTGeometry;
-import static org.neo4j.internal.kernel.api.procs.Neo4jTypes.NTNode;
-import static org.neo4j.internal.kernel.api.procs.Neo4jTypes.NTPath;
-import static org.neo4j.internal.kernel.api.procs.Neo4jTypes.NTPoint;
-import static org.neo4j.internal.kernel.api.procs.Neo4jTypes.NTRelationship;
-import static org.neo4j.kernel.api.proc.Context.DATABASE_API;
-import static org.neo4j.kernel.api.proc.Context.DEPENDENCY_RESOLVER;
-import static org.neo4j.kernel.api.proc.Context.KERNEL_TRANSACTION;
-import static org.neo4j.kernel.api.proc.Context.SECURITY_CONTEXT;
+import java.io.File;
+import java.util.Map;
+import java.util.function.Function;
+
+import static org.neo4j.internal.kernel.api.procs.Neo4jTypes.*;
+import static org.neo4j.kernel.api.proc.Context.*;
/**
* This is the main factory for creating database instances. It delegates creation to three different modules
@@ -127,8 +121,8 @@ default AvailabilityGuardInstaller availabilityGuardInstaller()
protected final DatabaseInfo databaseInfo;
private final Function editionFactory;
- public GraphDatabaseFacadeFactory( DatabaseInfo databaseInfo,
- Function editionFactory )
+ public GraphDatabaseFacadeFactory(DatabaseInfo databaseInfo,
+ Function editionFactory )
{
this.databaseInfo = databaseInfo;
this.editionFactory = editionFactory;
@@ -186,9 +180,6 @@ public GraphDatabaseFacade initFacade( File storeDir, Config config, final Depen
Procedures procedures = setupProcedures( platform, edition, graphDatabaseFacade );
platform.dependencies.satisfyDependency( new NonTransactionalDbmsOperations( procedures ) );
- //blob support
- platform.life.add( new BlobPropertyStoreService( procedures, storeDir, config, databaseInfo ) );
-
Logger msgLog = platform.logging.getInternalLog( getClass() ).infoLogger();
DatabaseManager databaseManager = edition.createDatabaseManager( graphDatabaseFacade, platform, edition, procedures, msgLog );
platform.life.add( databaseManager );
@@ -253,6 +244,11 @@ public GraphDatabaseFacade initFacade( File storeDir, Config config, final Depen
throw error;
}
+ // NOTE: pandadb
+ //blob support
+ PandaEventHub.publish(new GraphDatabaseStartedEvent(procedures, storeDir, config, databaseInfo));
+ //platform.life.add( new InstanceBoundServiceFactoryRegistryHolder( procedures, storeDir, config, databaseInfo ) );
+ // END-NOTE
return databaseFacade;
}
diff --git a/neo4j-hacking/src/main/scala/cn/pandadb/context/GraphDatabaseStartedEvent.scala b/neo4j-hacking/src/main/scala/cn/pandadb/context/GraphDatabaseStartedEvent.scala
new file mode 100644
index 00000000..5150fef8
--- /dev/null
+++ b/neo4j-hacking/src/main/scala/cn/pandadb/context/GraphDatabaseStartedEvent.scala
@@ -0,0 +1,16 @@
+package cn.pandadb.context
+
+import java.io.File
+
+import cn.pandadb.util.PandaEvent
+import org.neo4j.kernel.configuration.Config
+import org.neo4j.kernel.impl.factory.DatabaseInfo
+import org.neo4j.kernel.impl.proc.Procedures
+
+case class GraphDatabaseStartedEvent(proceduresService: Procedures,
+ storeDir: File,
+ neo4jConf: Config,
+ databaseInfo: DatabaseInfo)
+ extends PandaEvent {
+
+}
diff --git a/network-commons/pom.xml b/network-commons/pom.xml
new file mode 100644
index 00000000..02714dc4
--- /dev/null
+++ b/network-commons/pom.xml
@@ -0,0 +1,58 @@
+
+
+
+ parent
+ cn.pandadb
+ 0.0.2
+ ../
+
+ 4.0.0
+
+ cn.pandadb
+ network-commons
+ shared lib for driver/server
+
+
+
+ cn.pandadb
+ commons
+ ${pandadb.version}
+
+
+ net.neoremind
+ kraps-rpc_2.11
+
+
+
+
+
+
+
+ org.apache.curator
+ curator-recipes
+
+
+ org.neo4j
+ neo4j
+ test
+
+
+ cn.pandadb
+ neo4j-hacking
+ 0.0.2
+ test
+
+
+ junit
+ junit
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/network-commons/src/main/resources/log4j.properties b/network-commons/src/main/resources/log4j.properties
new file mode 100644
index 00000000..f9f8ea82
--- /dev/null
+++ b/network-commons/src/main/resources/log4j.properties
@@ -0,0 +1,15 @@
+log4j.rootLogger=DEBUG, stdout
+
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=[%d{HH:mm:ss:SSS}] %-5p %-20c{1} :: %m%n
+
+#log4j.logger.org.neo4j.values.storable=DEBUG
+log4j.logger.org.neo4j=DEBUG
+log4j.logger.org.neo4j.server=DEBUG
+log4j.logger.cn=DEBUG
+log4j.logger.org=WARN
+log4j.logger.io=WARN
+log4j.logger.java=WARN
+log4j.logger.org.quartz=WARN
+log4j.logger.eu.medsea.mimeutil=WARN
\ No newline at end of file
diff --git a/network-commons/src/main/scala/cn/pandadb/network/SerializeUtil.scala b/network-commons/src/main/scala/cn/pandadb/network/SerializeUtil.scala
new file mode 100644
index 00000000..86e24eec
--- /dev/null
+++ b/network-commons/src/main/scala/cn/pandadb/network/SerializeUtil.scala
@@ -0,0 +1,26 @@
+package cn.pandadb.network
+
+import java.io.{ByteArrayInputStream, ByteArrayOutputStream, ObjectInputStream, ObjectOutputStream}
+
+/**
+ * @Author: Airzihao
+ * @Description:
+ * @Date: Created at 11:50 2019/12/4
+ * @Modified By:
+ */
+object BytesTransform {
+
+ def serialize[T](o: T): Array[Byte] = {
+ val bos = new ByteArrayOutputStream()
+ val oos = new ObjectOutputStream(bos)
+ oos.writeObject(o)
+ oos.close()
+ bos.toByteArray
+ }
+
+ def deserialize[T](bytes: Array[Byte]): T = {
+ val bis = new ByteArrayInputStream(bytes)
+ val ois = new ObjectInputStream(bis)
+ ois.readObject.asInstanceOf[T]
+ }
+}
diff --git a/network-commons/src/main/scala/cn/pandadb/network/ZKClusterEventListener.scala b/network-commons/src/main/scala/cn/pandadb/network/ZKClusterEventListener.scala
new file mode 100644
index 00000000..4430f983
--- /dev/null
+++ b/network-commons/src/main/scala/cn/pandadb/network/ZKClusterEventListener.scala
@@ -0,0 +1,20 @@
+package cn.pandadb.network
+
+class ZKClusterEventListener() extends ClusterEventListener {
+ override def onEvent(event: ClusterEvent): Unit = {
+ event match {
+ // Not implemented.
+ case ClusterStateChanged() => ;
+ case NodeConnected(nodeAddress) => ;
+ case NodeDisconnected(nodeAddress) => ;
+ case ReadRequestAccepted() => ;
+ case WriteRequestAccepted() => ;
+ case ReadRequestCompleted() => ;
+ case WriteRequestCompleted() => ;
+ case MasterWriteNodeSeleted() => ;
+ case READY_TO_WRITE() => ;
+ case WRITE_FINISHED() => ;
+
+ }
+ }
+}
diff --git a/network-commons/src/main/scala/cn/pandadb/network/ZKPathConfig.scala b/network-commons/src/main/scala/cn/pandadb/network/ZKPathConfig.scala
new file mode 100644
index 00000000..1c624bd2
--- /dev/null
+++ b/network-commons/src/main/scala/cn/pandadb/network/ZKPathConfig.scala
@@ -0,0 +1,36 @@
+package cn.pandadb.network
+
+import org.apache.curator.framework.{CuratorFramework, CuratorFrameworkFactory}
+import org.apache.curator.retry.ExponentialBackoffRetry
+import org.apache.zookeeper.{CreateMode, ZooDefs}
+
+/**
+ * @Author: Airzihao
+ * @Description:
+ * @Date: Created in 20:41 2019/11/26
+ * @Modified By:
+ */
+object ZKPathConfig {
+ val registryPath = s"/PandaDB-v0.0.2"
+ val ordinaryNodesPath = registryPath + s"/ordinaryNodes"
+ val leaderNodePath = registryPath + s"/leaderNode"
+ val dataVersionPath = registryPath + s"/version"
+ val freshNodePath = registryPath + s"/freshNode"
+
+ def initZKPath(zkString: String): Unit = {
+ val _curator = CuratorFrameworkFactory.newClient(zkString,
+ new ExponentialBackoffRetry(1000, 3))
+ _curator.start()
+ val list = List(registryPath, ordinaryNodesPath, leaderNodePath, dataVersionPath, freshNodePath)
+ list.foreach(path => {
+ if (_curator.checkExists().forPath(path) == null) {
+ _curator.create()
+ .creatingParentsIfNeeded()
+ .withMode(CreateMode.PERSISTENT)
+ .withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE)
+ .forPath(path)
+ }
+ })
+ _curator.close()
+ }
+}
diff --git a/network-commons/src/main/scala/cn/pandadb/network/ZookeeperBasedClusterClient.scala b/network-commons/src/main/scala/cn/pandadb/network/ZookeeperBasedClusterClient.scala
new file mode 100644
index 00000000..d617cd1f
--- /dev/null
+++ b/network-commons/src/main/scala/cn/pandadb/network/ZookeeperBasedClusterClient.scala
@@ -0,0 +1,130 @@
+package cn.pandadb.network
+
+import scala.collection.JavaConverters._
+import org.apache.curator.framework.recipes.cache.PathChildrenCache.StartMode
+import org.apache.curator.framework.recipes.cache.{PathChildrenCache, PathChildrenCacheEvent, PathChildrenCacheListener}
+import org.apache.curator.framework.{CuratorFramework, CuratorFrameworkFactory}
+import org.apache.curator.retry.ExponentialBackoffRetry
+import org.apache.zookeeper.data.Stat
+import org.apache.zookeeper.{CreateMode, ZooDefs}
+
+import scala.concurrent.Future
+import scala.concurrent.ExecutionContext.Implicits.global
+
+/**
+ * @Author: Airzihao
+ * @Description:
+ * @Date: Created in 9:06 2019/11/26
+ * @Modified By:
+ */
+
+class ZookeeperBasedClusterClient(zkString: String) extends ClusterClient {
+
+ val zkServerAddress = zkString
+ val curator: CuratorFramework = CuratorFrameworkFactory.newClient(zkServerAddress,
+ new ExponentialBackoffRetry(1000, 3));
+ curator.start()
+
+ private var currentState: ClusterState = _
+ var listenerList: List[ZKClusterEventListener] = List[ZKClusterEventListener]()
+
+ private var availableNodes: Set[NodeAddress] = {
+ val pathArrayList = curator.getChildren.forPath(ZKPathConfig.ordinaryNodesPath).asScala
+ pathArrayList.map(NodeAddress.fromString(_)).toSet
+ }
+
+ addCuratorListener()
+
+ override def getWriteMasterNode(): NodeAddress = {
+ var leaderAddress = curator.getChildren().forPath(ZKPathConfig.leaderNodePath)
+
+ while (leaderAddress.isEmpty) {
+ Thread.sleep(500)
+ leaderAddress = curator.getChildren().forPath(ZKPathConfig.leaderNodePath)
+ }
+ NodeAddress.fromString(leaderAddress.get(0))
+ }
+
+ def getWriteMasterNode(inner: String): Option[NodeAddress] = {
+ val leaderAddress = curator.getChildren().forPath(ZKPathConfig.leaderNodePath)
+
+ if(leaderAddress.isEmpty) {
+ None
+ } else {
+ Some(NodeAddress.fromString(leaderAddress.get(0)))
+ }
+ }
+
+ override def getAllNodes(): Iterable[NodeAddress] = {
+ availableNodes
+ }
+
+ override def getCurrentState(): ClusterState = {
+ currentState
+ }
+
+ // add listener to listenerList, of no use at this period.
+ override def listen(listener: ClusterEventListener): Unit = {
+ listenerList = listener.asInstanceOf[ZKClusterEventListener] :: listenerList
+ }
+
+ override def waitFor(state: ClusterState): Unit = {
+ }
+
+ def getCurator(): CuratorFramework = {
+ curator
+ }
+
+ def getFreshNodeIp(): String = {
+ curator.getChildren.forPath(ZKPathConfig.freshNodePath).get(0)
+ }
+
+ def getClusterDataVersion(): Int = {
+ if (curator.checkExists().forPath(ZKPathConfig.dataVersionPath) == null) {
+ curator.create()
+ .creatingParentsIfNeeded()
+ .withMode(CreateMode.PERSISTENT)
+ .withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE)
+ .forPath(ZKPathConfig.dataVersionPath)
+ curator.setData().forPath(ZKPathConfig.dataVersionPath, BytesTransform.serialize(-1))
+ BytesTransform.deserialize(curator.getData.forPath(ZKPathConfig.dataVersionPath))
+ } else {
+ val stat = new Stat()
+ val version = curator.getData.storingStatIn(stat).forPath(ZKPathConfig.dataVersionPath)
+ // stat.version == 0, means not init
+ if (stat.getVersion == 0) {
+ curator.setData().forPath(ZKPathConfig.dataVersionPath, BytesTransform.serialize(-1))
+ BytesTransform.deserialize(curator.getData.forPath(ZKPathConfig.dataVersionPath))
+ } else {
+ BytesTransform.deserialize(version)
+ }
+ }
+ }
+
+ def addCuratorListener(): Unit = {
+ val nodesChildrenCache = new PathChildrenCache(curator, ZKPathConfig.ordinaryNodesPath, true)
+ nodesChildrenCache.start(StartMode.BUILD_INITIAL_CACHE)
+ nodesChildrenCache.getListenable().addListener(
+ new PathChildrenCacheListener {
+ override def childEvent(curatorFramework: CuratorFramework, pathChildrenCacheEvent: PathChildrenCacheEvent): Unit = {
+ try {
+ pathChildrenCacheEvent.getType() match {
+
+ case PathChildrenCacheEvent.Type.CHILD_ADDED =>
+ val nodeAddress = NodeAddress.fromString(pathChildrenCacheEvent.getData.getPath.split(s"/").last)
+ availableNodes += nodeAddress
+ for (listener <- listenerList) listener.onEvent(NodeConnected(nodeAddress));
+ case PathChildrenCacheEvent.Type.CHILD_REMOVED =>
+ val nodeAddress = NodeAddress.fromString(pathChildrenCacheEvent.getData.getPath.split(s"/").last)
+ availableNodes -= nodeAddress
+ for (listener <- listenerList) listener.onEvent(NodeDisconnected(NodeAddress.fromString(pathChildrenCacheEvent.getData.getPath)));
+ // What to do if a node's data is updated?
+ case PathChildrenCacheEvent.Type.CHILD_UPDATED => ;
+ case _ => ;
+ }
+ } catch { case ex: Exception => ex.printStackTrace() }
+ }
+ })
+ }
+
+}
diff --git a/network-commons/src/main/scala/cn/pandadb/network/events.scala b/network-commons/src/main/scala/cn/pandadb/network/events.scala
new file mode 100644
index 00000000..e28f853a
--- /dev/null
+++ b/network-commons/src/main/scala/cn/pandadb/network/events.scala
@@ -0,0 +1,45 @@
+package cn.pandadb.network
+
+trait ClusterEvent {
+
+}
+
+case class ClusterStateChanged() extends ClusterEvent {
+
+}
+
+case class NodeConnected(nodeAddress: NodeAddress) extends ClusterEvent {
+
+}
+
+case class NodeDisconnected(nodeAddress: NodeAddress) extends ClusterEvent {
+
+}
+
+case class ReadRequestAccepted() extends ClusterEvent {
+
+}
+
+case class WriteRequestAccepted() extends ClusterEvent {
+
+}
+
+case class ReadRequestCompleted() extends ClusterEvent {
+
+}
+
+case class WriteRequestCompleted() extends ClusterEvent {
+
+}
+
+case class MasterWriteNodeSeleted() extends ClusterEvent {
+
+}
+
+case class READY_TO_WRITE() extends ClusterEvent {
+
+}
+
+case class WRITE_FINISHED() extends ClusterEvent {
+
+}
\ No newline at end of file
diff --git a/network-commons/src/main/scala/cn/pandadb/network/internal/client/InternalRpcClient.scala b/network-commons/src/main/scala/cn/pandadb/network/internal/client/InternalRpcClient.scala
new file mode 100644
index 00000000..cacc0327
--- /dev/null
+++ b/network-commons/src/main/scala/cn/pandadb/network/internal/client/InternalRpcClient.scala
@@ -0,0 +1,31 @@
+package cn.pandadb.network.internal.client
+
+import cn.pandadb.network.internal.message.InternalRpcRequest
+import cn.pandadb.util.Logging
+import net.neoremind.kraps.RpcConf
+import net.neoremind.kraps.rpc.netty.NettyRpcEnvFactory
+import net.neoremind.kraps.rpc.{RpcAddress, RpcEnv, RpcEnvClientConfig}
+
+import scala.concurrent.Await
+import scala.concurrent.duration.Duration
+
+/**
+ * Created by bluejoe on 2019/11/25.
+ */
+class InternalRpcClient(rpcEnv: RpcEnv, host: String, port: Int) extends Logging {
+ val endPointRef = {
+ val rpcConf = new RpcConf()
+ val config = RpcEnvClientConfig(rpcConf, "pandadb-internal-client")
+ val rpcEnv: RpcEnv = NettyRpcEnvFactory.create(config)
+
+ rpcEnv.setupEndpointRef(RpcAddress(host, port), "pandadb-internal-client-service")
+ }
+
+ def close(): Unit = {
+ rpcEnv.stop(endPointRef)
+ }
+
+ def request[T >: InternalRpcRequest](message: Any): T = {
+ Await.result(endPointRef.ask(message), Duration.Inf);
+ }
+}
diff --git a/network-commons/src/main/scala/cn/pandadb/network/internal/message/InternalRpcMessage.scala b/network-commons/src/main/scala/cn/pandadb/network/internal/message/InternalRpcMessage.scala
new file mode 100644
index 00000000..1e26f666
--- /dev/null
+++ b/network-commons/src/main/scala/cn/pandadb/network/internal/message/InternalRpcMessage.scala
@@ -0,0 +1,20 @@
+package cn.pandadb.network.internal.message
+
+/**
+ * Created by bluejoe on 2019/11/25.
+ */
+trait InternalRpcRequest {
+
+}
+
+trait InternalRpcResponse {
+
+}
+
+case class AuthenticationRequest() extends InternalRpcRequest{
+
+}
+
+case class AuthenticationResponse() extends InternalRpcResponse{
+
+}
\ No newline at end of file
diff --git a/network-commons/src/main/scala/cn/pandadb/network/net.scala b/network-commons/src/main/scala/cn/pandadb/network/net.scala
new file mode 100644
index 00000000..e324c08b
--- /dev/null
+++ b/network-commons/src/main/scala/cn/pandadb/network/net.scala
@@ -0,0 +1,59 @@
+package cn.pandadb.network
+
+/**
+ * Created by bluejoe on 2019/11/21.
+ */
+case class NodeAddress(host: String, port: Int) {
+ def getAsString: String = {
+ host + ":" + port.toString
+ }
+}
+
+object NodeAddress {
+ def fromString(url: String, separator: String = ":"): NodeAddress = {
+ val pair = url.split(separator)
+ NodeAddress(pair(0), pair(1).toInt)
+ }
+}
+
+// used by server & driver
+trait ClusterClient {
+
+ def getWriteMasterNode(): NodeAddress;
+
+ def getAllNodes(): Iterable[NodeAddress];
+
+ def getCurrentState(): ClusterState;
+
+ def waitFor(state: ClusterState): Unit;
+
+ def listen(listener: ClusterEventListener): Unit;
+}
+
+trait ClusterEventListener {
+ def onEvent(event: ClusterEvent)
+}
+
+trait ClusterState {
+
+}
+
+case class LockedServing() extends ClusterState{
+
+}
+
+case class UnlockedServing() extends ClusterState{
+
+}
+
+case class PreWrite() extends ClusterState{
+
+}
+
+case class Writing() extends ClusterState{
+
+}
+
+case class Finished() extends ClusterState{
+
+}
\ No newline at end of file
diff --git a/network-commons/src/test/resources/test_pnode0.conf b/network-commons/src/test/resources/test_pnode0.conf
new file mode 100644
index 00000000..0ae98a18
--- /dev/null
+++ b/network-commons/src/test/resources/test_pnode0.conf
@@ -0,0 +1,37 @@
+dbms.security.auth_enabled=false
+dbms.connector.bolt.enabled=true
+dbms.connector.bolt.tls_level=OPTIONAL
+dbms.connector.bolt.listen_address=0.0.0.0:7685
+
+dbms.connector.http.enabled=true
+dbms.connector.http.listen_address=localhost:7469
+dbms.connector.https.enabled=false
+dbms.logs.http.enabled=true
+
+blob.plugins.conf=./cypher-plugins.xml
+
+#blob.storage=cn.pidb.engine.HBaseBlobValueStorage
+blob.storage.hbase.zookeeper.port=2181
+blob.storage.hbase.zookeeper.quorum=localhost
+blob.storage.hbase.auto_create_table=true
+blob.storage.hbase.table=PIDB_BLOB
+
+blob.aipm.modules.enabled=false
+blob.aipm.modules.dir=/usr/local/aipm/modules/
+
+#blob.storage=cn.pidb.engine.FileBlobValueStorage
+#blob.storage.file.dir=/tmp
+
+#dbms.active_database=testdb
+aipm.http.host.url=http://10.0.86.128:8081/
+
+# fake address just for test, supposed to be the real ip:port of localhost:boltPort
+#serviceAddress=10.0.88.11:1111
+# zookeeper revelant args
+zkServerAddress=10.0.86.26:2181,10.0.86.27:2181,10.0.86.70:2181
+sessionTimeout=20000
+connectionTimeout=10000
+registryPath=/pandaNodes
+ordinaryNodesPath=/pandaNodes/ordinaryNodes
+leaderNodePath=/pandaNodes/leaderNode
+localNodeAddress=10.0.88.11:1111
\ No newline at end of file
diff --git a/network-commons/src/test/scala/ZKConstantsTest.scala b/network-commons/src/test/scala/ZKConstantsTest.scala
new file mode 100644
index 00000000..4334fc03
--- /dev/null
+++ b/network-commons/src/test/scala/ZKConstantsTest.scala
@@ -0,0 +1,18 @@
+import cn.pandadb.network.ZKPathConfig
+import org.junit.{Assert, Test}
+
+/**
+ * @Author: Airzihao
+ * @Description:
+ * @Date: Created in 11:07 2019/11/26
+ * @Modified By:
+ */
+class ZKConstantsTest {
+
+ @Test
+ def testZKPathConfig(): Unit = {
+ Assert.assertEquals(s"/PandaDB-v0.0.2", ZKPathConfig.registryPath)
+ Assert.assertEquals(s"/PandaDB-v0.0.2/leaderNode", ZKPathConfig.leaderNodePath)
+ Assert.assertEquals(s"/PandaDB-v0.0.2/ordinaryNodes", ZKPathConfig.ordinaryNodesPath)
+ }
+}
diff --git a/network-commons/src/test/scala/ZKDiscoveryTest.scala b/network-commons/src/test/scala/ZKDiscoveryTest.scala
new file mode 100644
index 00000000..278fb1df
--- /dev/null
+++ b/network-commons/src/test/scala/ZKDiscoveryTest.scala
@@ -0,0 +1,103 @@
+//import cn.pandadb.network._
+//import cn.pandadb.server.ZKServiceRegistry
+//import org.apache.curator.framework.recipes.cache.PathChildrenCache.StartMode
+//import org.apache.curator.framework.recipes.cache.{PathChildrenCache, PathChildrenCacheEvent, PathChildrenCacheListener}
+//import org.apache.curator.framework.{CuratorFramework, CuratorFrameworkFactory}
+//import org.apache.curator.retry.ExponentialBackoffRetry
+//import org.junit.runners.MethodSorters
+//import org.junit.{Assert, FixMethodOrder, Test}
+//
+//import ZKDiscoveryTest.{listenerList, localNodeAddress, ordinadyNodeRegistry}
+///**
+// * @Author: Airzihao
+// * @Description:
+// * @Date: Created in 17:02 2019/11/26
+// * @Modified By:
+// */
+//
+//class FakeListener(listenerId: Int) {
+// val id = listenerId
+// var CHILD_ADDED = 0
+// var CHILD_REMOVED = 0
+// var path = s"";
+//}
+//object ZKDiscoveryTest {
+// val zkServerAddress = "10.0.86.26:2181";
+// val localNodeAddress = "10.0.88.11:1111"
+// val curator: CuratorFramework = CuratorFrameworkFactory.newClient(zkServerAddress,
+// new ExponentialBackoffRetry(1000, 3));
+// curator.start()
+//
+// val listenerList: List[FakeListener] = List(new FakeListener(1), new FakeListener(2))
+// val ordinadyNodeRegistry = new ZKServiceRegistry(zkServerAddress)
+//
+// val initListenerList = _addListener(curator, listenerList)
+//
+// private def _addListener(curator: CuratorFramework, listenerList: List[FakeListener]) {
+//
+// val nodesChildrenCache = new PathChildrenCache(curator, ZKPathConfig.ordinaryNodesPath, false)
+//
+// //caution: use sync method. POST_INITIAL_EVENT is an async method.
+// nodesChildrenCache.start(StartMode.BUILD_INITIAL_CACHE)
+//
+// val listener = new PathChildrenCacheListener {
+// override def childEvent(curatorFramework: CuratorFramework, pathChildrenCacheEvent: PathChildrenCacheEvent): Unit = {
+// try {
+// pathChildrenCacheEvent.getType() match {
+// case PathChildrenCacheEvent.Type.CHILD_ADDED =>
+// for (listener <- listenerList) {
+// listener.CHILD_ADDED = 1;
+// // if not splitted, returned: /pandaNodes/ordinaryNodes.10.0.88.11:1111
+// listener.path = pathChildrenCacheEvent.getData.getPath.split(s"/").last
+// }
+//
+// case PathChildrenCacheEvent.Type.CHILD_REMOVED =>
+// for (listener <- listenerList) {
+// listener.CHILD_REMOVED = 1;
+// listener.path = pathChildrenCacheEvent.getData.getPath
+// }
+// // What to do if a node's data is updated?
+// case PathChildrenCacheEvent.Type.CHILD_UPDATED => ;
+// case _ => ;
+// }
+// } catch { case ex: Exception => ex.printStackTrace() }
+// }
+// }
+// nodesChildrenCache.getListenable().addListener(listener)
+// }
+//}
+//
+//@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+//class ZKDiscoveryTest {
+//
+// @Test
+// def test0(): Unit = {
+// for (listener <- listenerList) {
+// Assert.assertEquals(0, listener.CHILD_ADDED)
+// Assert.assertEquals(0, listener.CHILD_REMOVED)
+// Assert.assertEquals("", listener.path)
+// }
+// }
+//
+// @Test
+// def test1(): Unit = {
+// ordinadyNodeRegistry.registerAsOrdinaryNode(NodeAddress.fromString(localNodeAddress))
+// Thread.sleep(1000)
+// for (listener <- listenerList) {
+// Assert.assertEquals(1, listener.CHILD_ADDED)
+// Assert.assertEquals(0, listener.CHILD_REMOVED)
+// Assert.assertEquals("10.0.88.11:1111", listener.path)
+// }
+// }
+//
+// @Test
+// def test2(): Unit = {
+// ordinadyNodeRegistry.unRegisterOrdinaryNode(NodeAddress.fromString(localNodeAddress))
+// Thread.sleep(1000)
+//
+// for (listener <- listenerList) {
+// Assert.assertEquals(1, listener.CHILD_ADDED)
+// Assert.assertEquals(1, listener.CHILD_REMOVED)
+// }
+// }
+//}
\ No newline at end of file
diff --git a/network-commons/src/test/scala/ZookeeperBasedClusterClientTest.scala b/network-commons/src/test/scala/ZookeeperBasedClusterClientTest.scala
new file mode 100644
index 00000000..c30f05ca
--- /dev/null
+++ b/network-commons/src/test/scala/ZookeeperBasedClusterClientTest.scala
@@ -0,0 +1,53 @@
+//import cn.pandadb.network.{NodeAddress, ZookeeperBasedClusterClient}
+//import cn.pandadb.server.ZKServiceRegistry
+//import org.junit.runners.MethodSorters
+//import org.junit.{Assert, FixMethodOrder, Test}
+//
+///**
+// * @Author: Airzihao
+// * @Description: add some cases to fully test the func.
+// * @Date: Created at 10:32 2019/11/27
+// * @Modified By:
+// */
+//
+//@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+//class ZookeeperBasedClusterClientTest {
+//
+// val zkString = "10.0.86.26:2181"
+// val localNodeAddress = "10.0.88.11:1111"
+//
+// val clusterClient = new ZookeeperBasedClusterClient(zkString)
+// val register = new ZKServiceRegistry(zkString)
+//
+// // empty at first
+// @Test
+// def test1(): Unit = {
+// Assert.assertEquals(true, clusterClient.getAllNodes().isEmpty)
+// }
+//
+// // getAllNodes, will get test node
+// @Test
+// def test2(): Unit = {
+// register.registerAsOrdinaryNode(NodeAddress.fromString(localNodeAddress))
+// Thread.sleep(1000)
+// Assert.assertEquals(false, clusterClient.getAllNodes().isEmpty)
+// Assert.assertEquals(NodeAddress.fromString("10.0.88.11:1111"), clusterClient.getAllNodes().iterator.next())
+// }
+//
+// // empty after test node unRegister itself
+// @Test
+// def test3(): Unit = {
+// register.unRegisterOrdinaryNode(NodeAddress.fromString(localNodeAddress))
+// Thread.sleep(1000)
+// Assert.assertEquals(true, clusterClient.getAllNodes().isEmpty)
+// }
+//
+// // test leader
+// @Test
+// def test4(): Unit = {
+// register.registerAsLeader(NodeAddress.fromString(localNodeAddress))
+// Thread.sleep(1000)
+// Assert.assertEquals(NodeAddress.fromString("10.0.88.11:1111"), clusterClient.getWriteMasterNode("").get)
+// }
+//
+//}
diff --git a/packaging/pom.xml b/packaging/pom.xml
new file mode 100644
index 00000000..e7048a56
--- /dev/null
+++ b/packaging/pom.xml
@@ -0,0 +1,90 @@
+
+
+ parent
+ cn.pandadb
+ 0.0.2
+
+ 4.0.0
+ cn.pandadb
+ packaging-build
+
+
+
+
+
+
+
+ cn.pandadb
+ tools
+ ${project.version}
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+ 3.1.0
+
+ false
+ pandadb-${project.version}
+ true
+ ${project.build.directory}
+
+
+ #{*}
+
+
+
+
+ jar-with-dependency
+ package
+
+ single
+
+
+
+ jar-with-dependencies
+
+ ${project.build.directory}/lib
+
+
+
+ community-unix-dist
+ package
+
+ single
+
+
+
+ src/main/assemblies/community-unix-dist.xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packaging/src/main/assemblies/community-unix-dist.xml b/packaging/src/main/assemblies/community-unix-dist.xml
new file mode 100644
index 00000000..8d8c6f07
--- /dev/null
+++ b/packaging/src/main/assemblies/community-unix-dist.xml
@@ -0,0 +1,40 @@
+
+
+
+ unix
+
+ tar.gz
+
+
+
+
+
+
+ src/main/distribution/text/community
+
+ keep
+ true
+ 0755
+ 0644
+
+ **/.keep
+
+
+
+
+ ${project.build.directory}/lib
+ lib
+ true
+ 0755
+ 0755
+
+ *.jar
+
+
+
+
+
+
+
diff --git a/packaging/src/main/distribution/text/community/bin/.keep b/packaging/src/main/distribution/text/community/bin/.keep
new file mode 100644
index 00000000..e69de29b
diff --git a/packaging/src/main/distribution/text/community/bin/pandadb.sh b/packaging/src/main/distribution/text/community/bin/pandadb.sh
new file mode 100644
index 00000000..588c6ac6
--- /dev/null
+++ b/packaging/src/main/distribution/text/community/bin/pandadb.sh
@@ -0,0 +1,586 @@
+#!/usr/bin/env bash
+
+# Callers may provide the following environment variables to customize this script:
+# * JAVA_HOME
+# * JAVA_CMD
+# * PANDADB_HOME
+# * PANDADB_CONF
+# * PANDADB_START_WAIT
+
+
+set -o errexit -o nounset -o pipefail
+[[ "${TRACE:-}" ]] && set -o xtrace
+
+declare -r PROGRAM="$(basename "$0")"
+
+# Sets up the standard environment for running PandaDB shell scripts.
+#
+# Provides these environment variables:
+# PANDADB_HOME
+# PANDADB_CONF
+# PANDADB_DATA
+# PANDADB_LIB
+# PANDADB_LOGS
+# PANDADB_PIDFILE
+# PANDADB_PLUGINS
+# one per config setting, with dots converted to underscores
+#
+setup_environment() {
+ _setup_calculated_paths
+ _read_config
+ _setup_configurable_paths
+}
+
+setup_heap() {
+ if [[ -n "${HEAP_SIZE:-}" ]]; then
+ JAVA_MEMORY_OPTS_XMS="-Xms${HEAP_SIZE}"
+ JAVA_MEMORY_OPTS_XMX="-Xmx${HEAP_SIZE}"
+ fi
+}
+
+build_classpath() {
+ CLASSPATH="${PANDADB_PLUGINS}:${PANDADB_CONF}:${PANDADB_LIB}/*:${PANDADB_PLUGINS}/*"
+
+ # augment with tools.jar, will need JDK
+ if [ "${JAVA_HOME:-}" ]; then
+ JAVA_TOOLS="${JAVA_HOME}/lib/tools.jar"
+ if [[ -e $JAVA_TOOLS ]]; then
+ CLASSPATH="${CLASSPATH}:${JAVA_TOOLS}"
+ fi
+ fi
+}
+
+detect_os() {
+ if uname -s | grep -q Darwin; then
+ DIST_OS="macosx"
+ elif [[ -e /etc/gentoo-release ]]; then
+ DIST_OS="gentoo"
+ else
+ DIST_OS="other"
+ fi
+}
+
+setup_memory_opts() {
+ # In some cases the heap size may have already been set before we get here, from e.g. HEAP_SIZE env.variable, if so then skip
+ if [[ -n "${dbms_memory_heap_initial_size:-}" && -z "${JAVA_MEMORY_OPTS_XMS-}" ]]; then
+ local mem="${dbms_memory_heap_initial_size}"
+ if ! [[ ${mem} =~ .*[gGmMkK] ]]; then
+ mem="${mem}m"
+ cat >&2 <&2 <&1 | awk -F '"' '/version/ {print $2}')
+ if [[ $JAVA_VERSION = "1."* ]]; then
+ if [[ "${JAVA_VERSION}" < "1.8" ]]; then
+ echo "ERROR! PandaDB cannot be started using java version ${JAVA_VERSION}. "
+ _show_java_help
+ exit 1
+ fi
+ if ! ("${version_command[@]}" 2>&1 | egrep -q "(Java HotSpot\\(TM\\)|OpenJDK|IBM) (64-Bit Server|Server|Client|J9) VM"); then
+ unsupported_runtime_warning
+ fi
+ elif [[ $JAVA_VERSION = "11"* ]]; then
+ if ! ("${version_command[@]}" 2>&1 | egrep -q "(Java HotSpot\\(TM\\)|OpenJDK|IBM) (64-Bit Server|Server|Client|J9) VM"); then
+ unsupported_runtime_warning
+ fi
+ else
+ unsupported_runtime_warning
+ fi
+}
+
+unsupported_runtime_warning() {
+ echo "WARNING! You are using an unsupported Java runtime. "
+ _show_java_help
+}
+
+# Resolve a path relative to $PANDADB_HOME. Don't resolve if
+# the path is absolute.
+resolve_path() {
+ orig_filename=$1
+ if [[ ${orig_filename} == /* ]]; then
+ filename="${orig_filename}"
+ else
+ filename="${PANDADB_HOME}/${orig_filename}"
+ fi
+ echo "${filename}"
+}
+
+call_main_class() {
+ setup_environment
+ check_java
+ build_classpath
+ EXTRA_JVM_ARGUMENTS="-Dfile.encoding=UTF-8"
+ class_name=$1
+ shift
+
+ export PANDADB_HOME PANDADB_CONF
+
+ exec "${JAVA_CMD}" ${JAVA_OPTS:-} ${JAVA_MEMORY_OPTS_XMS-} ${JAVA_MEMORY_OPTS_XMX-} \
+ -classpath "${CLASSPATH}" \
+ ${EXTRA_JVM_ARGUMENTS:-} \
+ $class_name "$@"
+}
+
+_find_java_cmd() {
+ [[ "${JAVA_CMD:-}" ]] && return
+ detect_os
+ _find_java_home
+
+ if [[ "${JAVA_HOME:-}" ]] ; then
+ JAVA_CMD="${JAVA_HOME}/bin/java"
+ if [[ ! -f "${JAVA_CMD}" ]]; then
+ echo "ERROR: JAVA_HOME is incorrectly defined as ${JAVA_HOME} (the executable ${JAVA_CMD} does not exist)"
+ exit 1
+ fi
+ else
+ if [ "${DIST_OS}" != "macosx" ] ; then
+ # Don't use default java on Darwin because it displays a misleading dialog box
+ JAVA_CMD="$(which java || true)"
+ fi
+ fi
+
+ if [[ ! "${JAVA_CMD:-}" ]]; then
+ echo "ERROR: Unable to find Java executable."
+ _show_java_help
+ exit 1
+ fi
+}
+
+_find_java_home() {
+ [[ "${JAVA_HOME:-}" ]] && return
+
+ case "${DIST_OS}" in
+ "macosx")
+ JAVA_HOME="$(/usr/libexec/java_home -v 1.8)"
+ ;;
+ "gentoo")
+ JAVA_HOME="$(java-config --jre-home)"
+ ;;
+ esac
+}
+
+_show_java_help() {
+ echo "* Please use Oracle(R) Java(TM) 8, OpenJDK(TM) or IBM J9 to run PandaDB."
+}
+
+_setup_calculated_paths() {
+ if [[ -z "${PANDADB_HOME:-}" ]]; then
+ PANDADB_HOME="$(cd "$(dirname "$0")"/.. && pwd)"
+ fi
+ : "${PANDADB_CONF:="${PANDADB_HOME}/conf"}"
+ readonly PANDADB_HOME PANDADB_CONF
+}
+
+_read_config() {
+ # - plain key-value pairs become environment variables
+ # - keys have '.' chars changed to '_'
+ # - keys of the form KEY.# (where # is a number) are concatenated into a single environment variable named KEY
+ parse_line() {
+ line="$1"
+ if [[ "${line}" =~ ^([^#\s][^=]+)=(.+)$ ]]; then
+ key="${BASH_REMATCH[1]//./_}"
+ value="${BASH_REMATCH[2]}"
+ if [[ "${key}" =~ ^(.*)_([0-9]+)$ ]]; then
+ key="${BASH_REMATCH[1]}"
+ fi
+ # Ignore keys that start with a number because export ${key}= will fail - it is not valid for a bash env var to start with a digit
+ if [[ ! "${key}" =~ ^[0-9]+.*$ ]]; then
+ if [[ "${!key:-}" ]]; then
+ export ${key}="${!key} ${value}"
+ else
+ export ${key}="${value}"
+ fi
+ else
+ echo >&2 "WARNING: Ignoring key ${key}, environment variables cannot start with a number."
+ fi
+ fi
+ }
+
+ for file in "pandadb.conf"; do
+ path="${PANDADB_CONF}/${file}"
+ if [ -e "${path}" ]; then
+ while read line; do
+ parse_line "${line}"
+ done <"${path}"
+ fi
+ done
+}
+
+_setup_configurable_paths() {
+ PANDADB_DATA=$(resolve_path "${dbms_directories_data:-data}")
+ PANDADB_LIB=$(resolve_path "${dbms_directories_lib:-lib}")
+ PANDADB_LOGS=$(resolve_path "${dbms_directories_logs:-logs}")
+ PANDADB_PLUGINS=$(resolve_path "${dbms_directories_plugins:-plugins}")
+ PANDADB_RUN=$(resolve_path "${dbms_directories_run:-run}")
+ PANDADB_CERTS=$(resolve_path "${dbms_directories_certificates:-certificates}")
+
+ if [ -z "${dbms_directories_import:-}" ]; then
+ PANDADB_IMPORT="NOT SET"
+ else
+ PANDADB_IMPORT=$(resolve_path "${dbms_directories_import:-}")
+ fi
+
+ readonly PANDADB_DATA PANDADB_LIB PANDADB_LOGS PANDADB_PLUGINS PANDADB_RUN PANDADB_IMPORT PANDADB_CERTS
+}
+
+print_configurable_paths() {
+ cat </dev/null || unset PANDADB_PID
+ fi
+}
+
+check_limits() {
+ detect_os
+ if [ "${DIST_OS}" != "macosx" ] ; then
+ ALLOWED_OPEN_FILES="$(ulimit -n)"
+
+ if [ "${ALLOWED_OPEN_FILES}" -lt "${MIN_ALLOWED_OPEN_FILES}" ]; then
+ echo "WARNING: Max ${ALLOWED_OPEN_FILES} open files allowed, minimum of ${MIN_ALLOWED_OPEN_FILES} recommended. See the Neo4j manual."
+ fi
+ fi
+}
+
+setup_java_opts() {
+ JAVA_OPTS=("-server" ${JAVA_MEMORY_OPTS_XMS-} ${JAVA_MEMORY_OPTS_XMX-})
+
+ if [[ "${dbms_logs_gc_enabled:-}" = "true" ]]; then
+ if [[ "${JAVA_VERSION}" = "1.8"* ]]; then
+ # JAVA 8 GC logging setup
+ JAVA_OPTS+=("-Xloggc:${PANDADB_LOGS}/gc.log" \
+ "-XX:+UseGCLogFileRotation" \
+ "-XX:NumberOfGCLogFiles=${dbms_logs_gc_rotation_keep_number:-5}" \
+ "-XX:GCLogFileSize=${dbms_logs_gc_rotation_size:-20m}")
+ if [[ -n "${dbms_logs_gc_options:-}" ]]; then
+ JAVA_OPTS+=(${dbms_logs_gc_options}) # unquoted to split on spaces
+ else
+ JAVA_OPTS+=("-XX:+PrintGCDetails" "-XX:+PrintGCDateStamps" "-XX:+PrintGCApplicationStoppedTime" \
+ "-XX:+PrintPromotionFailure" "-XX:+PrintTenuringDistribution")
+ fi
+ else
+ # JAVA 9 and newer GC logging setup
+ local gc_options
+ if [[ -n "${dbms_logs_gc_options:-}" ]]; then
+ gc_options="${dbms_logs_gc_options}"
+ else
+ gc_options="-Xlog:gc*,safepoint,age*=trace"
+ fi
+ gc_options+=":file=${PANDADB_LOGS}/gc.log::filecount=${dbms_logs_gc_rotation_keep_number:-5},filesize=${dbms_logs_gc_rotation_size:-20m}"
+ JAVA_OPTS+=(${gc_options})
+ fi
+ fi
+
+ if [[ -n "${dbms_jvm_additional:-}" ]]; then
+ JAVA_OPTS+=(${dbms_jvm_additional}) # unquoted to split on spaces
+ fi
+}
+
+assemble_command_line() {
+ retval=("${JAVA_CMD}" "-cp" "${CLASSPATH}" "${JAVA_OPTS[@]}" "-Dfile.encoding=UTF-8" "${MAIN_CLASS}" \
+ "${PANDADB_HOME}/data" "${PANDADB_CONF}/pandadb.conf")
+}
+
+do_console() {
+ check_status
+ if [[ "${PANDADB_PID:-}" ]] ; then
+ echo "PandaDB is already running (pid ${PANDADB_PID})."
+ exit 1
+ fi
+
+ echo "Starting PandaDB."
+
+ check_limits
+ build_classpath
+
+ assemble_command_line
+ command_line=("${retval[@]}")
+ exec "${command_line[@]}"
+}
+
+do_start() {
+ check_status
+ if [[ "${PANDADB_PID:-}" ]] ; then
+ echo "PandaDB is already running (pid ${PANDADB_PID})."
+ exit 0
+ fi
+ # check dir for pidfile exists
+ if [[ ! -d $(dirname "${PANDADB_PIDFILE}") ]]; then
+ mkdir -p $(dirname "${PANDADB_PIDFILE}")
+ fi
+
+ echo "Starting PandaDB."
+
+ check_limits
+ build_classpath
+
+ assemble_command_line
+ command_line=("${retval[@]}")
+ nohup "${command_line[@]}" >>"${CONSOLE_LOG}" 2>&1 &
+ echo "$!" >"${PANDADB_PIDFILE}"
+
+ : "${PANDADB_START_WAIT:=5}"
+ end="$((SECONDS+PANDADB_START_WAIT))"
+ while true; do
+ check_status
+
+ if [[ "${PANDADB_PID:-}" ]]; then
+ break
+ fi
+
+ if [[ "${SECONDS}" -ge "${end}" ]]; then
+ echo "Unable to start. See ${CONSOLE_LOG} for details."
+ rm "${PANDADB_PIDFILE}"
+ return 1
+ fi
+
+ sleep 1
+ done
+
+ print_start_message
+ echo "See ${CONSOLE_LOG} for current status."
+}
+
+do_stop() {
+ check_status
+
+ if [[ ! "${PANDADB_PID:-}" ]] ; then
+ echo "PandaDB not running"
+ [ -e "${PANDADB_PIDFILE}" ] && rm "${PANDADB_PIDFILE}"
+ return 0
+ else
+ echo -n "Stopping PandaDB."
+ end="$((SECONDS+SHUTDOWN_TIMEOUT))"
+ while true; do
+ check_status
+
+ if [[ ! "${PANDADB_PID:-}" ]]; then
+ echo " stopped"
+ [ -e "${PANDADB_PIDFILE}" ] && rm "${PANDADB_PIDFILE}"
+ return 0
+ fi
+
+ kill "${PANDADB_PID}" 2>/dev/null || true
+
+ if [[ "${SECONDS}" -ge "${end}" ]]; then
+ echo " failed to stop"
+ echo "PandaDB (pid ${PANDADB_PID}) took more than ${SHUTDOWN_TIMEOUT} seconds to stop."
+ echo "Please see ${CONSOLE_LOG} for details."
+ return 1
+ fi
+
+ echo -n "."
+ sleep 1
+ done
+ fi
+}
+
+do_status() {
+ check_status
+ if [[ ! "${PANDADB_PID:-}" ]] ; then
+ echo "PandaDB is not running"
+ exit 3
+ else
+ echo "PandaDB is running at pid ${PANDADB_PID}"
+ fi
+}
+
+do_version() {
+ build_classpath
+
+ assemble_command_line
+ command_line=("${retval[@]}" "--version")
+ exec "${command_line[@]}"
+}
+
+send_command_to_all_nodes(){
+ if [[ "${pandadb_cluster_nodes:-}" ]] ; then
+ echo "PandaDB cluster nodes: ${pandadb_cluster_nodes}"
+ nodes=$(echo $pandadb_cluster_nodes|tr "," "\n")
+ for node in ${nodes[@]}; do
+ ssh_cmd="ssh ${node} $1"
+ echo "${ssh_cmd}"
+ ssh ${node} $1
+ done
+ else
+ echo "WARNING: pandadb.cluster.nodes is not set in configure file."
+ fi
+}
+
+setup_java () {
+ check_java
+ setup_java_opts
+ setup_arbiter_options
+}
+
+
+main() {
+ setup_environment
+ CONSOLE_LOG="${PANDADB_LOGS}/pandadb.log"
+ PANDADB_PIDFILE="${PANDADB_RUN}/pandadb.pid"
+ readonly CONSOLE_LOG PANDADB_PIDFILE
+
+ case "${1:-}" in
+ console)
+ setup_java
+ print_active_database
+ print_configurable_paths
+ do_console
+ ;;
+
+ start)
+ START_NODE_KIND="pnode"
+ setup_java
+ print_active_database
+ print_configurable_paths
+ do_start
+ ;;
+
+ start-all-nodes)
+ send_command_to_all_nodes "source /etc/profile;cd \$PANDADB_HOME;pandadb.sh start;"
+ ;;
+
+ start-watch-dog)
+ START_NODE_KIND="watch-dog"
+ setup_java
+ print_active_database
+ print_configurable_paths
+ do_start
+ ;;
+
+ stop)
+ setup_arbiter_options
+ do_stop
+ ;;
+
+ stop-all-nodes)
+ send_command_to_all_nodes "source /etc/profile;cd \$PANDADB_HOME;pandadb.sh stop;"
+ ;;
+
+ restart)
+ setup_java
+ do_stop
+ do_start
+ ;;
+
+ status)
+ do_status
+ ;;
+#
+# --version|version)
+# setup_java
+# do_version
+# ;;
+
+ help)
+ echo "Usage: ${PROGRAM} { console | start | start-watch-dog | stop | restart | start-all-nodes | stop-all-nodes | status | version }"
+ ;;
+
+ *)
+ echo >&2 "Usage: ${PROGRAM} { console | start | start-watch-dog | stop | restart | start-all-nodes | stop-all-nodes | status | version }"
+ exit 1
+ ;;
+ esac
+}
+
+main "$@"
diff --git a/packaging/src/main/distribution/text/community/conf/pandadb.conf b/packaging/src/main/distribution/text/community/conf/pandadb.conf
new file mode 100644
index 00000000..4e6487c5
--- /dev/null
+++ b/packaging/src/main/distribution/text/community/conf/pandadb.conf
@@ -0,0 +1,412 @@
+#*****************************************************************
+# PandaDB configuration
+#*****************************************************************
+
+# IP or Hostname of cluster nodes
+pandadb.cluster.nodes=10.0.82.216,10.0.82.217,10.0.82.218
+
+
+blob.plugins.conf=./cypher-plugins.xml
+
+#blob.storage=cn.pidb.engine.HBaseBlobValueStorage
+#blob.storage.hbase.zookeeper.port=2181
+#blob.storage.hbase.zookeeper.quorum=localhost
+#blob.storage.hbase.auto_create_table=true
+#blob.storage.hbase.table=PIDB_BLOB
+
+#blob.storage=org.neo4j.kernel.impl.blob.DefaultLocalFileSystemBlobValueStorage
+#blob.storage.file.dir=/tmp
+
+#blob.aipm.modules.enabled=false
+#blob.aipm.modules.dir=/usr/local/aipm/modules/
+aipm.http.host.url=http://127.0.0.1:8081/
+
+
+# zk config
+zookeeper.address=10.0.82.216:2181,10.0.82.217:2181
+
+# this node service address
+node.server.address=10.0.82.216:7687
+
+# node communication rpc config
+rpc.port=1224
+
+
+# external storage config
+external.property.storage.enabled=true
+
+# solr external storage config
+# external.properties.store.factory=cn.pandadb.externalprops.InSolrPropertyNodeStoreFactory
+# external.properties.store.solr.zk=10.0.82.216:2181,10.0.82.217:2181,10.0.82.218:2181
+# external.properties.store.solr.collection=pandaDB
+
+# ElasticSearch external storage config
+external.properties.store.factory=cn.pandadb.externalprops.InElasticSearchPropertyNodeStoreFactory
+external.properties.store.es.host=10.0.82.216
+external.properties.store.es.port=9200
+external.properties.store.es.schema=http
+external.properties.store.es.scroll.size=1000
+external.properties.store.es.scroll.time.minutes=10
+external.properties.store.es.index=test-0119
+external.properties.store.es.type=nodes
+
+
+#*****************************************************************
+# Neo4j configuration
+#
+# For more details and a complete list of settings, please see
+# https://neo4j.com/docs/operations-manual/current/reference/configuration-settings/
+#*****************************************************************
+
+# The name of the database to mount
+#dbms.active_database=graph.db
+
+# Paths of directories in the installation.
+#dbms.directories.data=data
+#dbms.directories.plugins=plugins
+#dbms.directories.certificates=certificates
+#dbms.directories.logs=logs
+#dbms.directories.lib=lib
+#dbms.directories.run=run
+
+# This setting constrains all `LOAD CSV` import files to be under the `import` directory. Remove or comment it out to
+# allow files to be loaded from anywhere in the filesystem; this introduces possible security problems. See the
+# `LOAD CSV` section of the manual for details.
+dbms.directories.import=import
+
+# Whether requests to Neo4j are authenticated.
+# To disable authentication, uncomment this line
+dbms.security.auth_enabled=false
+
+# Enable this to be able to upgrade a store from an older version.
+#dbms.allow_upgrade=true
+
+# Java Heap Size: by default the Java heap size is dynamically
+# calculated based on available system resources.
+# Uncomment these lines to set specific initial and maximum
+# heap size.
+#dbms.memory.heap.initial_size=512m
+#dbms.memory.heap.max_size=512m
+
+# The amount of memory to use for mapping the store files, in bytes (or
+# kilobytes with the 'k' suffix, megabytes with 'm' and gigabytes with 'g').
+# If Neo4j is running on a dedicated server, then it is generally recommended
+# to leave about 2-4 gigabytes for the operating system, give the JVM enough
+# heap to hold all your transaction state and query context, and then leave the
+# rest for the page cache.
+# The default page cache memory assumes the machine is dedicated to running
+# Neo4j, and is heuristically set to 50% of RAM minus the max Java heap size.
+#dbms.memory.pagecache.size=10g
+
+#*****************************************************************
+# Network connector configuration
+#*****************************************************************
+
+# With default configuration Neo4j only accepts local connections.
+# To accept non-local connections, uncomment this line:
+dbms.connectors.default_listen_address=0.0.0.0
+
+# You can also choose a specific network interface, and configure a non-default
+# port for each connector, by setting their individual listen_address.
+
+# The address at which this server can be reached by its clients. This may be the server's IP address or DNS name, or
+# it may be the address of a reverse proxy which sits in front of the server. This setting may be overridden for
+# individual connectors below.
+dbms.connectors.default_advertised_address=0.0.0.0
+
+# You can also choose a specific advertised hostname or IP address, and
+# configure an advertised port for each connector, by setting their
+# individual advertised_address.
+
+# Bolt connector
+dbms.connector.bolt.enabled=true
+dbms.connector.bolt.tls_level=OPTIONAL
+dbms.connector.bolt.listen_address=:7685
+
+# HTTP Connector. There can be zero or one HTTP connectors.
+dbms.connector.http.enabled=true
+dbms.connector.http.listen_address=:7474
+
+# HTTPS Connector. There can be zero or one HTTPS connectors.
+#dbms.connector.https.enabled=true
+#dbms.connector.https.listen_address=:7473
+
+# Number of Neo4j worker threads.
+#dbms.threads.worker_count=
+
+#*****************************************************************
+# SSL system configuration
+#*****************************************************************
+
+# Names of the SSL policies to be used for the respective components.
+
+# The legacy policy is a special policy which is not defined in
+# the policy configuration section, but rather derives from
+# dbms.directories.certificates and associated files
+# (by default: neo4j.key and neo4j.cert). Its use will be deprecated.
+
+# The policies to be used for connectors.
+#
+# N.B: Note that a connector must be configured to support/require
+# SSL/TLS for the policy to actually be utilized.
+#
+# see: dbms.connector.*.tls_level
+
+#bolt.ssl_policy=legacy
+#https.ssl_policy=legacy
+
+#*****************************************************************
+# SSL policy configuration
+#*****************************************************************
+
+# Each policy is configured under a separate namespace, e.g.
+# dbms.ssl.policy..*
+#
+# The example settings below are for a new policy named 'default'.
+
+# The base directory for cryptographic objects. Each policy will by
+# default look for its associated objects (keys, certificates, ...)
+# under the base directory.
+#
+# Every such setting can be overridden using a full path to
+# the respective object, but every policy will by default look
+# for cryptographic objects in its base location.
+#
+# Mandatory setting
+
+#dbms.ssl.policy.default.base_directory=certificates/default
+
+# Allows the generation of a fresh private key and a self-signed
+# certificate if none are found in the expected locations. It is
+# recommended to turn this off again after keys have been generated.
+#
+# Keys should in general be generated and distributed offline
+# by a trusted certificate authority (CA) and not by utilizing
+# this mode.
+
+#dbms.ssl.policy.default.allow_key_generation=false
+
+# Enabling this makes it so that this policy ignores the contents
+# of the trusted_dir and simply resorts to trusting everything.
+#
+# Use of this mode is discouraged. It would offer encryption but no security.
+
+#dbms.ssl.policy.default.trust_all=false
+
+# The private key for the default SSL policy. By default a file
+# named private.key is expected under the base directory of the policy.
+# It is mandatory that a key can be found or generated.
+
+#dbms.ssl.policy.default.private_key=
+
+# The private key for the default SSL policy. By default a file
+# named public.crt is expected under the base directory of the policy.
+# It is mandatory that a certificate can be found or generated.
+
+#dbms.ssl.policy.default.public_certificate=
+
+# The certificates of trusted parties. By default a directory named
+# 'trusted' is expected under the base directory of the policy. It is
+# mandatory to create the directory so that it exists, because it cannot
+# be auto-created (for security purposes).
+#
+# To enforce client authentication client_auth must be set to 'require'!
+
+#dbms.ssl.policy.default.trusted_dir=
+
+# Client authentication setting. Values: none, optional, require
+# The default is to require client authentication.
+#
+# Servers are always authenticated unless explicitly overridden
+# using the trust_all setting. In a mutual authentication setup this
+# should be kept at the default of require and trusted certificates
+# must be installed in the trusted_dir.
+
+#dbms.ssl.policy.default.client_auth=require
+
+# It is possible to verify the hostname that the client uses
+# to connect to the remote server. In order for this to work, the server public
+# certificate must have a valid CN and/or matching Subject Alternative Names.
+
+# Note that this is irrelevant on host side connections (sockets receiving
+# connections).
+
+# To enable hostname verification client side on nodes, set this to true.
+
+#dbms.ssl.policy.default.verify_hostname=false
+
+# A comma-separated list of allowed TLS versions.
+# By default only TLSv1.2 is allowed.
+
+#dbms.ssl.policy.default.tls_versions=
+
+# A comma-separated list of allowed ciphers.
+# The default ciphers are the defaults of the JVM platform.
+
+#dbms.ssl.policy.default.ciphers=
+
+#*****************************************************************
+# Logging configuration
+#*****************************************************************
+
+# To enable HTTP logging, uncomment this line
+dbms.logs.http.enabled=true
+
+# Number of HTTP logs to keep.
+#dbms.logs.http.rotation.keep_number=5
+
+# Size of each HTTP log that is kept.
+#dbms.logs.http.rotation.size=20m
+
+# To enable GC Logging, uncomment this line
+#dbms.logs.gc.enabled=true
+
+# GC Logging Options
+# see http://docs.oracle.com/cd/E19957-01/819-0084-10/pt_tuningjava.html#wp57013 for more information.
+#dbms.logs.gc.options=-XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime -XX:+PrintPromotionFailure -XX:+PrintTenuringDistribution
+
+# For Java 9 and newer GC Logging Options
+# see https://docs.oracle.com/javase/10/tools/java.htm#JSWOR-GUID-BE93ABDC-999C-4CB5-A88B-1994AAAC74D5
+#dbms.logs.gc.options=-Xlog:gc*,safepoint,age*=trace
+
+# Number of GC logs to keep.
+#dbms.logs.gc.rotation.keep_number=5
+
+# Size of each GC log that is kept.
+#dbms.logs.gc.rotation.size=20m
+
+# Log level for the debug log. One of DEBUG, INFO, WARN and ERROR. Be aware that logging at DEBUG level can be very verbose.
+#dbms.logs.debug.level=INFO
+
+# Size threshold for rotation of the debug log. If set to zero then no rotation will occur. Accepts a binary suffix "k",
+# "m" or "g".
+#dbms.logs.debug.rotation.size=20m
+
+# Maximum number of history files for the internal log.
+#dbms.logs.debug.rotation.keep_number=7
+
+#*****************************************************************
+# Miscellaneous configuration
+#*****************************************************************
+
+# Enable this to specify a parser other than the default one.
+#cypher.default_language_version=2.3
+
+# Determines if Cypher will allow using file URLs when loading data using
+# `LOAD CSV`. Setting this value to `false` will cause Neo4j to fail `LOAD CSV`
+# clauses that load data from the file system.
+#dbms.security.allow_csv_import_from_file_urls=true
+
+
+# Value of the Access-Control-Allow-Origin header sent over any HTTP or HTTPS
+# connector. This defaults to '*', which allows broadest compatibility. Note
+# that any URI provided here limits HTTP/HTTPS access to that URI only.
+#dbms.security.http_access_control_allow_origin=*
+
+# Value of the HTTP Strict-Transport-Security (HSTS) response header. This header
+# tells browsers that a webpage should only be accessed using HTTPS instead of HTTP.
+# It is attached to every HTTPS response. Setting is not set by default so
+# 'Strict-Transport-Security' header is not sent. Value is expected to contain
+# directives like 'max-age', 'includeSubDomains' and 'preload'.
+#dbms.security.http_strict_transport_security=
+
+# Retention policy for transaction logs needed to perform recovery and backups.
+dbms.tx_log.rotation.retention_policy=1 days
+
+# Only allow read operations from this Neo4j instance. This mode still requires
+# write access to the directory for lock purposes.
+#dbms.read_only=false
+
+# Comma separated list of JAX-RS packages containing JAX-RS resources, one
+# package name for each mountpoint. The listed package names will be loaded
+# under the mountpoints specified. Uncomment this line to mount the
+# org.neo4j.examples.server.unmanaged.HelloWorldResource.java from
+# neo4j-server-examples under /examples/unmanaged, resulting in a final URL of
+# http://localhost:7474/examples/unmanaged/helloworld/{nodeId}
+#dbms.unmanaged_extension_classes=org.neo4j.examples.server.unmanaged=/examples/unmanaged
+
+# A comma separated list of procedures and user defined functions that are allowed
+# full access to the database through unsupported/insecure internal APIs.
+#dbms.security.procedures.unrestricted=my.extensions.example,my.procedures.*
+
+# A comma separated list of procedures to be loaded by default.
+# Leaving this unconfigured will load all procedures found.
+#dbms.security.procedures.whitelist=apoc.coll.*,apoc.load.*
+
+#********************************************************************
+# JVM Parameters
+#********************************************************************
+
+# G1GC generally strikes a good balance between throughput and tail
+# latency, without too much tuning.
+dbms.jvm.additional=-XX:+UseG1GC
+
+# Have common exceptions keep producing stack traces, so they can be
+# debugged regardless of how often logs are rotated.
+dbms.jvm.additional=-XX:-OmitStackTraceInFastThrow
+
+# Make sure that `initmemory` is not only allocated, but committed to
+# the process, before starting the database. This reduces memory
+# fragmentation, increasing the effectiveness of transparent huge
+# pages. It also reduces the possibility of seeing performance drop
+# due to heap-growing GC events, where a decrease in available page
+# cache leads to an increase in mean IO response time.
+# Try reducing the heap memory, if this flag degrades performance.
+dbms.jvm.additional=-XX:+AlwaysPreTouch
+
+# Trust that non-static final fields are really final.
+# This allows more optimizations and improves overall performance.
+# NOTE: Disable this if you use embedded mode, or have extensions or dependencies that may use reflection or
+# serialization to change the value of final fields!
+dbms.jvm.additional=-XX:+UnlockExperimentalVMOptions
+dbms.jvm.additional=-XX:+TrustFinalNonStaticFields
+
+# Disable explicit garbage collection, which is occasionally invoked by the JDK itself.
+dbms.jvm.additional=-XX:+DisableExplicitGC
+
+# Remote JMX monitoring, uncomment and adjust the following lines as needed. Absolute paths to jmx.access and
+# jmx.password files are required.
+# Also make sure to update the jmx.access and jmx.password files with appropriate permission roles and passwords,
+# the shipped configuration contains only a read only role called 'monitor' with password 'Neo4j'.
+# For more details, see: http://download.oracle.com/javase/8/docs/technotes/guides/management/agent.html
+# On Unix based systems the jmx.password file needs to be owned by the user that will run the server,
+# and have permissions set to 0600.
+# For details on setting these file permissions on Windows see:
+# http://docs.oracle.com/javase/8/docs/technotes/guides/management/security-windows.html
+#dbms.jvm.additional=-Dcom.sun.management.jmxremote.port=3637
+#dbms.jvm.additional=-Dcom.sun.management.jmxremote.authenticate=true
+#dbms.jvm.additional=-Dcom.sun.management.jmxremote.ssl=false
+#dbms.jvm.additional=-Dcom.sun.management.jmxremote.password.file=/absolute/path/to/conf/jmx.password
+#dbms.jvm.additional=-Dcom.sun.management.jmxremote.access.file=/absolute/path/to/conf/jmx.access
+
+# Some systems cannot discover host name automatically, and need this line configured:
+#dbms.jvm.additional=-Djava.rmi.server.hostname=$THE_NEO4J_SERVER_HOSTNAME
+
+# Expand Diffie Hellman (DH) key size from default 1024 to 2048 for DH-RSA cipher suites used in server TLS handshakes.
+# This is to protect the server from any potential passive eavesdropping.
+dbms.jvm.additional=-Djdk.tls.ephemeralDHKeySize=2048
+
+# This mitigates a DDoS vector.
+dbms.jvm.additional=-Djdk.tls.rejectClientInitiatedRenegotiation=true
+
+# This filter prevents deserialization of arbitrary objects via java object serialization, addressing potential vulnerabilities.
+# By default this filter whitelists all neo4j classes, as well as classes from the hazelcast library and the java standard library.
+# These defaults should only be modified by expert users!
+# For more details (including filter syntax) see: https://openjdk.java.net/jeps/290
+#dbms.jvm.additional=-Djdk.serialFilter=java.**;org.neo4j.**;com.neo4j.**;com.hazelcast.**;net.sf.ehcache.Element;com.sun.proxy.*;org.openjdk.jmh.**;!*
+
+#********************************************************************
+# Wrapper Windows NT/2000/XP Service Properties
+#********************************************************************
+# WARNING - Do not modify any of these properties when an application
+# using this configuration file has been installed as a service.
+# Please uninstall the service before modifying this section. The
+# service can then be reinstalled.
+
+# Name of the service
+dbms.windows_service_name=neo4j
+
+#********************************************************************
+# Other Neo4j system properties
+#********************************************************************
+dbms.jvm.additional=-Dunsupported.dbms.udc.source=tarball
diff --git a/packaging/src/main/distribution/text/community/cypher-plugins.xml b/packaging/src/main/distribution/text/community/cypher-plugins.xml
new file mode 100644
index 00000000..d7f018f3
--- /dev/null
+++ b/packaging/src/main/distribution/text/community/cypher-plugins.xml
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packaging/src/main/distribution/text/community/data/databases/.keep b/packaging/src/main/distribution/text/community/data/databases/.keep
new file mode 100644
index 00000000..e69de29b
diff --git a/packaging/src/main/distribution/text/community/import/.keep b/packaging/src/main/distribution/text/community/import/.keep
new file mode 100644
index 00000000..e69de29b
diff --git a/packaging/src/main/distribution/text/community/plugins/.keep b/packaging/src/main/distribution/text/community/plugins/.keep
new file mode 100644
index 00000000..e69de29b
diff --git a/packaging/src/main/distribution/text/community/run/.keep b/packaging/src/main/distribution/text/community/run/.keep
new file mode 100644
index 00000000..e69de29b
diff --git a/pom.xml b/pom.xml
index 5c304e0e..9e774c4b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,112 +4,299 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0
- cn.graiph
- graiphdb-2019
- 1.0-SNAPSHOT
+ cn.pandadb
+ parent
+ pom
+ 0.0.2
+
+ java-driver
+ blob-commons
+ blob-feature
+ aipm-library
+ external-properties
+ server
+ commons
+ neo4j-hacking
+ network-commons
+ tools
+ itest
+ packaging
+
UTF-8
+ UTF-8
1.8
1.8
2.11.8
2.11
2.11
+ 0.0.2
-
- org.scala-lang.modules
- scala-parser-combinators_2.11
- 1.1.1
+ org.scala-lang
+ scala-library
- info.debatty
- java-string-similarity
- RELEASE
+ org.scala-lang
+ scala-compiler
- com.google.code.findbugs
- jsr305
- 3.0.0
-
-
- org.reactivestreams
- reactive-streams
- 1.0.2
-
-
- io.projectreactor
- reactor-core
- 3.2.6.RELEASE
-
-
- org.springframework
- spring-context
- 4.0.0.RELEASE
-
-
- commons-io
- commons-io
- 2.6
-
-
- commons-codec
- commons-codec
- 1.11
-
-
- eu.medsea.mimeutil
- mime-util
- 2.1.3
-
-
- org.apache.httpcomponents
- httpclient
- 4.5.7
-
-
- junit
- junit
- 4.12
-
-
- org.neo4j
- neo4j
- 3.5.6
-
-
- org.neo4j.app
- neo4j-server
- 3.5.6
-
-
-
- org.apache.solr
- solr-solrj
- 6.0.0
+ org.scala-lang
+ scala-reflect
org.slf4j
slf4j-log4j12
- 1.7.25
org.slf4j
slf4j-api
- 1.7.25
-
-
- org.scalatest
- scalatest_${scala.compat.version}
- 3.0.0
- test
-
-
- org.apache.zookeeper
- zookeeper
- 3.4.14
+
+
+
+
+ org.scala-lang
+ scala-library
+ ${scala.version}
+
+
+ org.scala-lang
+ scala-compiler
+ ${scala.version}
+
+
+ org.scala-lang
+ scala-reflect
+ ${scala.version}
+
+
+
+ org.scala-lang.modules
+ scala-parser-combinators_2.11
+ 1.1.1
+
+
+ info.debatty
+ java-string-similarity
+ RELEASE
+
+
+ com.google.code.findbugs
+ jsr305
+ 3.0.0
+
+
+ org.reactivestreams
+ reactive-streams
+ 1.0.2
+
+
+ io.projectreactor
+ reactor-core
+ 3.2.6.RELEASE
+
+
+ org.springframework
+ spring-context
+ 4.0.0.RELEASE
+
+
+ commons-io
+ commons-io
+ 2.6
+
+
+ commons-codec
+ commons-codec
+ 1.11
+
+
+ eu.medsea.mimeutil
+ mime-util
+ 2.1.3
+
+
+ net.neoremind
+ kraps-rpc_2.11
+ 1.0.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ org.apache.httpcomponents
+ httpclient
+ 4.5.7
+
+
+ junit
+ junit
+ 4.13.1
+
+
+ org.neo4j
+ neo4j
+ 3.5.6
+
+
+ org.neo4j.app
+ neo4j-server
+ 3.5.6
+
+
+ org.apache.solr
+ solr-solrj
+ 6.0.0
+
+
+ org.slf4j
+ slf4j-log4j12
+ 1.7.25
+
+
+ org.slf4j
+ slf4j-api
+ 1.7.25
+
+
+ org.scalatest
+ scalatest_${scala.compat.version}
+ 3.0.0
+ test
+
+
+ org.apache.httpcomponents
+ httpmime
+ 4.5.7
+
+
+ org.apache.curator
+ curator-recipes
+ 2.10.0
+
+
+
+
+
+
+ org.scalastyle
+ scalastyle-maven-plugin
+ 1.0.0
+
+ false
+ true
+ true
+ false
+ ${basedir}/src/main/scala
+ ${basedir}/src/test/scala
+ scalastyle-config.xml
+ ${basedir}/target/scalastyle-output.xml
+ ${project.build.sourceEncoding}
+ ${project.reporting.outputEncoding}
+
+
+
+
+ check
+
+
+
+
+
+
+ net.alchim31.maven
+ scala-maven-plugin
+ 3.2.1
+
+
+ scala-compile-first
+ process-resources
+
+ add-source
+ compile
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ 3.0.2
+
+
+
+ default-jar
+
+
+
+ true
+
+
+ ${project.organization.url}
+ ${moduleName}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/review/bluejoe_review.xml b/review/bluejoe_review.xml
new file mode 100644
index 00000000..f05bca5b
--- /dev/null
+++ b/review/bluejoe_review.xml
@@ -0,0 +1,106 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Solved.
+
+
+
+
+ deleted.
+
+
+
+ I'm reconstruct this class.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ At first, I designed the trait, supposed that there maybe another registry center which is not zk based.
+
+
+
+
+
+
+
+
+
+ if cannot create a new Client, will reinvoke itself
+
+
+
+
+
+
\ No newline at end of file
diff --git a/scalastyle-config.xml b/scalastyle-config.xml
new file mode 100644
index 00000000..0eb18643
--- /dev/null
+++ b/scalastyle-config.xml
@@ -0,0 +1,277 @@
+
+
+
+ Scalastyle standard configuration
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ARROW, EQUALS, ELSE, TRY, CATCH, FINALLY, LARROW, RARROW
+
+
+
+
+
+ ARROW, EQUALS, COMMA, COLON, IF, ELSE, DO, WHILE, FOR, MATCH, TRY, CATCH, FINALLY, LARROW, RARROW
+
+
+
+
+
+
+ (\r|)\n(\s*)(\r|)\n(\s*)(\r|)\n
+
+
+
+
+
+
+
+
+ ^println$
+
+
+
+
+ (\.toUpperCase|\.toLowerCase)(?!(\(|\(Locale.ROOT\)))
+
+
+
+
+ throw new \w+Error\(
+
+
+
+
+ throw new RuntimeException\(
+
+
+
+
+
+ COMMA
+
+
+
+
+ \)\{
+
+
+
+
+ (?m)^(\s*)/[*][*].*$(\r|)\n^\1 [*]
+ Use Javadoc style indentation for multiline comments
+
+
+
+ case[^\n>]*=>\s*\{
+ Omit braces in case clauses.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ org.apache.curator advised
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 10240
+
+
+
+
+ 300
+
+
+
+
+ 10
+
+
+
+
+ 50
+
+
+
+
+
+
+
+
+
+
+ -1,0,1,2,3
+
+
+
diff --git a/server/pom.xml b/server/pom.xml
new file mode 100644
index 00000000..3d7efb09
--- /dev/null
+++ b/server/pom.xml
@@ -0,0 +1,87 @@
+
+
+
+ parent
+ cn.pandadb
+ 0.0.2
+ ../
+
+ 4.0.0
+
+ cn.pandadb
+ server
+
+
+
+ cn.pandadb
+ commons
+ ${pandadb.version}
+ compile
+
+
+ cn.pandadb
+ network-commons
+ ${pandadb.version}
+ compile
+
+
+ cn.pandadb
+ blob-feature
+ ${pandadb.version}
+ compile
+
+
+ cn.pandadb
+ external-properties
+ ${pandadb.version}
+ compile
+
+
+ cn.pandadb
+ java-driver
+ ${pandadb.version}
+ compile
+
+
+ org.neo4j.app
+ neo4j-server
+
+
+ org.scala-lang.modules
+ scala-parser-combinators_2.11
+
+
+ org.springframework
+ spring-context
+
+
+
+ com.google.code.gson
+ gson
+ 2.8.5
+
+
+
+
+
+
+ net.alchim31.maven
+ scala-maven-plugin
+ 3.2.1
+
+
+ scala-compile-first
+ process-resources
+
+ add-source
+ compile
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/externel-properties/java/org/neo4j/bolt/v1/runtime/BoltAuthenticationHelper.java b/server/src/main/java/org/neo4j/bolt/v1/runtime/BoltAuthenticationHelper.java
similarity index 52%
rename from src/externel-properties/java/org/neo4j/bolt/v1/runtime/BoltAuthenticationHelper.java
rename to server/src/main/java/org/neo4j/bolt/v1/runtime/BoltAuthenticationHelper.java
index d9b2e538..a3693a2d 100644
--- a/src/externel-properties/java/org/neo4j/bolt/v1/runtime/BoltAuthenticationHelper.java
+++ b/server/src/main/java/org/neo4j/bolt/v1/runtime/BoltAuthenticationHelper.java
@@ -19,8 +19,9 @@
*/
package org.neo4j.bolt.v1.runtime;
-import java.util.Map;
-
+import cn.pandadb.server.internode.PNodeStatementProcessor;
+import cn.pandadb.server.watchdog.ForwardedStatementProcessor;
+import cn.pandadb.util.GlobalContext;
import org.neo4j.bolt.runtime.BoltConnectionFatality;
import org.neo4j.bolt.runtime.BoltStateMachineSPI;
import org.neo4j.bolt.runtime.StateMachineContext;
@@ -28,40 +29,36 @@
import org.neo4j.bolt.security.auth.AuthenticationResult;
import org.neo4j.values.storable.Values;
-public class BoltAuthenticationHelper
-{
- public static boolean IS_DISPATCHER_NODE = true;
+import java.util.Map;
- public static boolean processAuthentication( String userAgent, Map authToken, StateMachineContext context ) throws BoltConnectionFatality
- {
- try
- {
+public class BoltAuthenticationHelper {
+
+ public static boolean processAuthentication(String userAgent, Map authToken, StateMachineContext context) throws BoltConnectionFatality {
+ try {
BoltStateMachineSPI boltSpi = context.boltSpi();
- AuthenticationResult authResult = boltSpi.authenticate( authToken );
+ AuthenticationResult authResult = boltSpi.authenticate(authToken);
String username = authResult.getLoginContext().subject().username();
- context.authenticatedAsUser( username, userAgent );
-
- StatementProcessor statementProcessor = new TransactionStateMachine( boltSpi.transactionSpi(), authResult, context.clock() );
- //NOTE: dispatcher node or gnode?
- if(IS_DISPATCHER_NODE) {
- statementProcessor = new DispatchedStatementProcessor(statementProcessor, null);
+ context.authenticatedAsUser(username, userAgent);
+ StatementProcessor statementProcessor = new TransactionStateMachine(boltSpi.transactionSpi(), authResult, context.clock());
+ //NOTE: pandadb
+ //is watch dog
+ if(GlobalContext.isWatchDog()) {
+ statementProcessor = new ForwardedStatementProcessor(statementProcessor, boltSpi.transactionSpi());
}
-
- context.connectionState().setStatementProcessor( statementProcessor );
-
- if ( authResult.credentialsExpired() )
- {
- context.connectionState().onMetadata( "credentials_expired", Values.TRUE );
+ else {
+ statementProcessor = new PNodeStatementProcessor(statementProcessor, boltSpi.transactionSpi());
}
- context.connectionState().onMetadata( "server", Values.stringValue( boltSpi.version() ) );
- boltSpi.udcRegisterClient( userAgent );
-
+ //NOTE
+ context.connectionState().setStatementProcessor(statementProcessor);
+ if (authResult.credentialsExpired()) {
+ context.connectionState().onMetadata("credentials_expired", Values.TRUE);
+ }
+ context.connectionState().onMetadata("server", Values.stringValue(boltSpi.version()));
+ boltSpi.udcRegisterClient(userAgent);
return true;
- }
- catch ( Throwable t )
- {
- context.handleFailure( t, true );
+ } catch (Throwable t) {
+ context.handleFailure(t, true);
return false;
}
}
diff --git a/src/graiph-database/resources/browser/23eaba762d31f7c6f4d6.worker.js b/server/src/main/resources/browser/23eaba762d31f7c6f4d6.worker.js
similarity index 100%
rename from src/graiph-database/resources/browser/23eaba762d31f7c6f4d6.worker.js
rename to server/src/main/resources/browser/23eaba762d31f7c6f4d6.worker.js
diff --git a/src/graiph-database/resources/browser/app-d69f0f140465c60d7038.js b/server/src/main/resources/browser/app-d69f0f140465c60d7038.js
similarity index 100%
rename from src/graiph-database/resources/browser/app-d69f0f140465c60d7038.js
rename to server/src/main/resources/browser/app-d69f0f140465c60d7038.js
diff --git a/src/graiph-database/resources/browser/assets/click-next-f6af67414a67800d96d1163212644fed.png b/server/src/main/resources/browser/assets/click-next-f6af67414a67800d96d1163212644fed.png
similarity index 100%
rename from src/graiph-database/resources/browser/assets/click-next-f6af67414a67800d96d1163212644fed.png
rename to server/src/main/resources/browser/assets/click-next-f6af67414a67800d96d1163212644fed.png
diff --git a/src/graiph-database/resources/browser/assets/community-b5f64fe3d7d0a6384ba965cf55fcc319.jpg b/server/src/main/resources/browser/assets/community-b5f64fe3d7d0a6384ba965cf55fcc319.jpg
similarity index 100%
rename from src/graiph-database/resources/browser/assets/community-b5f64fe3d7d0a6384ba965cf55fcc319.jpg
rename to server/src/main/resources/browser/assets/community-b5f64fe3d7d0a6384ba965cf55fcc319.jpg
diff --git a/src/graiph-database/resources/browser/assets/customer-orders-6d6e459e9f19ee1031deee909694da2d.png b/server/src/main/resources/browser/assets/customer-orders-6d6e459e9f19ee1031deee909694da2d.png
similarity index 100%
rename from src/graiph-database/resources/browser/assets/customer-orders-6d6e459e9f19ee1031deee909694da2d.png
rename to server/src/main/resources/browser/assets/customer-orders-6d6e459e9f19ee1031deee909694da2d.png
diff --git a/src/graiph-database/resources/browser/assets/fonts/Inconsolata-Bold.ttf b/server/src/main/resources/browser/assets/fonts/Inconsolata-Bold.ttf
similarity index 100%
rename from src/graiph-database/resources/browser/assets/fonts/Inconsolata-Bold.ttf
rename to server/src/main/resources/browser/assets/fonts/Inconsolata-Bold.ttf
diff --git a/src/graiph-database/resources/browser/assets/fonts/Inconsolata-Regular.ttf b/server/src/main/resources/browser/assets/fonts/Inconsolata-Regular.ttf
similarity index 100%
rename from src/graiph-database/resources/browser/assets/fonts/Inconsolata-Regular.ttf
rename to server/src/main/resources/browser/assets/fonts/Inconsolata-Regular.ttf
diff --git a/src/graiph-database/resources/browser/assets/fonts/OpenSans-Bold.ttf b/server/src/main/resources/browser/assets/fonts/OpenSans-Bold.ttf
similarity index 100%
rename from src/graiph-database/resources/browser/assets/fonts/OpenSans-Bold.ttf
rename to server/src/main/resources/browser/assets/fonts/OpenSans-Bold.ttf
diff --git a/src/graiph-database/resources/browser/assets/fonts/OpenSans-BoldItalic.ttf b/server/src/main/resources/browser/assets/fonts/OpenSans-BoldItalic.ttf
similarity index 100%
rename from src/graiph-database/resources/browser/assets/fonts/OpenSans-BoldItalic.ttf
rename to server/src/main/resources/browser/assets/fonts/OpenSans-BoldItalic.ttf
diff --git a/src/graiph-database/resources/browser/assets/fonts/OpenSans-ExtraBold.ttf b/server/src/main/resources/browser/assets/fonts/OpenSans-ExtraBold.ttf
similarity index 100%
rename from src/graiph-database/resources/browser/assets/fonts/OpenSans-ExtraBold.ttf
rename to server/src/main/resources/browser/assets/fonts/OpenSans-ExtraBold.ttf
diff --git a/src/graiph-database/resources/browser/assets/fonts/OpenSans-ExtraBoldItalic.ttf b/server/src/main/resources/browser/assets/fonts/OpenSans-ExtraBoldItalic.ttf
similarity index 100%
rename from src/graiph-database/resources/browser/assets/fonts/OpenSans-ExtraBoldItalic.ttf
rename to server/src/main/resources/browser/assets/fonts/OpenSans-ExtraBoldItalic.ttf
diff --git a/src/graiph-database/resources/browser/assets/fonts/OpenSans-Italic.ttf b/server/src/main/resources/browser/assets/fonts/OpenSans-Italic.ttf
similarity index 100%
rename from src/graiph-database/resources/browser/assets/fonts/OpenSans-Italic.ttf
rename to server/src/main/resources/browser/assets/fonts/OpenSans-Italic.ttf
diff --git a/src/graiph-database/resources/browser/assets/fonts/OpenSans-Light.ttf b/server/src/main/resources/browser/assets/fonts/OpenSans-Light.ttf
similarity index 100%
rename from src/graiph-database/resources/browser/assets/fonts/OpenSans-Light.ttf
rename to server/src/main/resources/browser/assets/fonts/OpenSans-Light.ttf
diff --git a/src/graiph-database/resources/browser/assets/fonts/OpenSans-LightItalic.ttf b/server/src/main/resources/browser/assets/fonts/OpenSans-LightItalic.ttf
similarity index 100%
rename from src/graiph-database/resources/browser/assets/fonts/OpenSans-LightItalic.ttf
rename to server/src/main/resources/browser/assets/fonts/OpenSans-LightItalic.ttf
diff --git a/src/graiph-database/resources/browser/assets/fonts/OpenSans-Regular.ttf b/server/src/main/resources/browser/assets/fonts/OpenSans-Regular.ttf
similarity index 100%
rename from src/graiph-database/resources/browser/assets/fonts/OpenSans-Regular.ttf
rename to server/src/main/resources/browser/assets/fonts/OpenSans-Regular.ttf
diff --git a/src/graiph-database/resources/browser/assets/fonts/OpenSans-Semibold.ttf b/server/src/main/resources/browser/assets/fonts/OpenSans-Semibold.ttf
similarity index 100%
rename from src/graiph-database/resources/browser/assets/fonts/OpenSans-Semibold.ttf
rename to server/src/main/resources/browser/assets/fonts/OpenSans-Semibold.ttf
diff --git a/src/graiph-database/resources/browser/assets/fonts/OpenSans-SemiboldItalic.ttf b/server/src/main/resources/browser/assets/fonts/OpenSans-SemiboldItalic.ttf
similarity index 100%
rename from src/graiph-database/resources/browser/assets/fonts/OpenSans-SemiboldItalic.ttf
rename to server/src/main/resources/browser/assets/fonts/OpenSans-SemiboldItalic.ttf
diff --git a/src/graiph-database/resources/browser/assets/fonts/fontawesome-webfont.eot b/server/src/main/resources/browser/assets/fonts/fontawesome-webfont.eot
similarity index 100%
rename from src/graiph-database/resources/browser/assets/fonts/fontawesome-webfont.eot
rename to server/src/main/resources/browser/assets/fonts/fontawesome-webfont.eot
diff --git a/src/graiph-database/resources/browser/assets/fonts/fontawesome-webfont.svg b/server/src/main/resources/browser/assets/fonts/fontawesome-webfont.svg
similarity index 100%
rename from src/graiph-database/resources/browser/assets/fonts/fontawesome-webfont.svg
rename to server/src/main/resources/browser/assets/fonts/fontawesome-webfont.svg
diff --git a/src/graiph-database/resources/browser/assets/fonts/fontawesome-webfont.ttf b/server/src/main/resources/browser/assets/fonts/fontawesome-webfont.ttf
similarity index 100%
rename from src/graiph-database/resources/browser/assets/fonts/fontawesome-webfont.ttf
rename to server/src/main/resources/browser/assets/fonts/fontawesome-webfont.ttf
diff --git a/src/graiph-database/resources/browser/assets/fonts/fontawesome-webfont.woff b/server/src/main/resources/browser/assets/fonts/fontawesome-webfont.woff
similarity index 100%
rename from src/graiph-database/resources/browser/assets/fonts/fontawesome-webfont.woff
rename to server/src/main/resources/browser/assets/fonts/fontawesome-webfont.woff
diff --git a/src/graiph-database/resources/browser/assets/fonts/fontawesome-webfont.woff2 b/server/src/main/resources/browser/assets/fonts/fontawesome-webfont.woff2
similarity index 100%
rename from src/graiph-database/resources/browser/assets/fonts/fontawesome-webfont.woff2
rename to server/src/main/resources/browser/assets/fonts/fontawesome-webfont.woff2
diff --git a/src/graiph-database/resources/browser/assets/fonts/neo4j-world.eot b/server/src/main/resources/browser/assets/fonts/neo4j-world.eot
similarity index 100%
rename from src/graiph-database/resources/browser/assets/fonts/neo4j-world.eot
rename to server/src/main/resources/browser/assets/fonts/neo4j-world.eot
diff --git a/src/graiph-database/resources/browser/assets/fonts/neo4j-world.svg b/server/src/main/resources/browser/assets/fonts/neo4j-world.svg
similarity index 100%
rename from src/graiph-database/resources/browser/assets/fonts/neo4j-world.svg
rename to server/src/main/resources/browser/assets/fonts/neo4j-world.svg
diff --git a/src/graiph-database/resources/browser/assets/fonts/neo4j-world.ttf b/server/src/main/resources/browser/assets/fonts/neo4j-world.ttf
similarity index 100%
rename from src/graiph-database/resources/browser/assets/fonts/neo4j-world.ttf
rename to server/src/main/resources/browser/assets/fonts/neo4j-world.ttf
diff --git a/src/graiph-database/resources/browser/assets/fonts/neo4j-world.woff b/server/src/main/resources/browser/assets/fonts/neo4j-world.woff
similarity index 100%
rename from src/graiph-database/resources/browser/assets/fonts/neo4j-world.woff
rename to server/src/main/resources/browser/assets/fonts/neo4j-world.woff
diff --git a/src/graiph-database/resources/browser/assets/fonts/query-plan-operator-cost.svg b/server/src/main/resources/browser/assets/fonts/query-plan-operator-cost.svg
similarity index 100%
rename from src/graiph-database/resources/browser/assets/fonts/query-plan-operator-cost.svg
rename to server/src/main/resources/browser/assets/fonts/query-plan-operator-cost.svg
diff --git a/src/graiph-database/resources/browser/assets/fonts/query-plan-operator-details.svg b/server/src/main/resources/browser/assets/fonts/query-plan-operator-details.svg
similarity index 100%
rename from src/graiph-database/resources/browser/assets/fonts/query-plan-operator-details.svg
rename to server/src/main/resources/browser/assets/fonts/query-plan-operator-details.svg
diff --git a/src/graiph-database/resources/browser/assets/fonts/query-plan-operator-rows.svg b/server/src/main/resources/browser/assets/fonts/query-plan-operator-rows.svg
similarity index 100%
rename from src/graiph-database/resources/browser/assets/fonts/query-plan-operator-rows.svg
rename to server/src/main/resources/browser/assets/fonts/query-plan-operator-rows.svg
diff --git a/src/graiph-database/resources/browser/assets/fonts/query-plan.svg b/server/src/main/resources/browser/assets/fonts/query-plan.svg
similarity index 100%
rename from src/graiph-database/resources/browser/assets/fonts/query-plan.svg
rename to server/src/main/resources/browser/assets/fonts/query-plan.svg
diff --git a/src/graiph-database/resources/browser/assets/fonts/streamline.eot b/server/src/main/resources/browser/assets/fonts/streamline.eot
similarity index 100%
rename from src/graiph-database/resources/browser/assets/fonts/streamline.eot
rename to server/src/main/resources/browser/assets/fonts/streamline.eot
diff --git a/src/graiph-database/resources/browser/assets/fonts/streamline.svg b/server/src/main/resources/browser/assets/fonts/streamline.svg
similarity index 100%
rename from src/graiph-database/resources/browser/assets/fonts/streamline.svg
rename to server/src/main/resources/browser/assets/fonts/streamline.svg
diff --git a/src/graiph-database/resources/browser/assets/fonts/streamline.ttf b/server/src/main/resources/browser/assets/fonts/streamline.ttf
similarity index 100%
rename from src/graiph-database/resources/browser/assets/fonts/streamline.ttf
rename to server/src/main/resources/browser/assets/fonts/streamline.ttf
diff --git a/src/graiph-database/resources/browser/assets/fonts/streamline.woff b/server/src/main/resources/browser/assets/fonts/streamline.woff
similarity index 100%
rename from src/graiph-database/resources/browser/assets/fonts/streamline.woff
rename to server/src/main/resources/browser/assets/fonts/streamline.woff
diff --git a/src/graiph-database/resources/browser/assets/images/device-icons/android-chrome-144x144.png b/server/src/main/resources/browser/assets/images/device-icons/android-chrome-144x144.png
similarity index 100%
rename from src/graiph-database/resources/browser/assets/images/device-icons/android-chrome-144x144.png
rename to server/src/main/resources/browser/assets/images/device-icons/android-chrome-144x144.png
diff --git a/src/graiph-database/resources/browser/assets/images/device-icons/android-chrome-192x192.png b/server/src/main/resources/browser/assets/images/device-icons/android-chrome-192x192.png
similarity index 100%
rename from src/graiph-database/resources/browser/assets/images/device-icons/android-chrome-192x192.png
rename to server/src/main/resources/browser/assets/images/device-icons/android-chrome-192x192.png
diff --git a/src/graiph-database/resources/browser/assets/images/device-icons/android-chrome-36x36.png b/server/src/main/resources/browser/assets/images/device-icons/android-chrome-36x36.png
similarity index 100%
rename from src/graiph-database/resources/browser/assets/images/device-icons/android-chrome-36x36.png
rename to server/src/main/resources/browser/assets/images/device-icons/android-chrome-36x36.png
diff --git a/src/graiph-database/resources/browser/assets/images/device-icons/android-chrome-48x48.png b/server/src/main/resources/browser/assets/images/device-icons/android-chrome-48x48.png
similarity index 100%
rename from src/graiph-database/resources/browser/assets/images/device-icons/android-chrome-48x48.png
rename to server/src/main/resources/browser/assets/images/device-icons/android-chrome-48x48.png
diff --git a/src/graiph-database/resources/browser/assets/images/device-icons/android-chrome-72x72.png b/server/src/main/resources/browser/assets/images/device-icons/android-chrome-72x72.png
similarity index 100%
rename from src/graiph-database/resources/browser/assets/images/device-icons/android-chrome-72x72.png
rename to server/src/main/resources/browser/assets/images/device-icons/android-chrome-72x72.png
diff --git a/src/graiph-database/resources/browser/assets/images/device-icons/android-chrome-96x96.png b/server/src/main/resources/browser/assets/images/device-icons/android-chrome-96x96.png
similarity index 100%
rename from src/graiph-database/resources/browser/assets/images/device-icons/android-chrome-96x96.png
rename to server/src/main/resources/browser/assets/images/device-icons/android-chrome-96x96.png
diff --git a/src/graiph-database/resources/browser/assets/images/device-icons/apple-touch-icon-114x114.png b/server/src/main/resources/browser/assets/images/device-icons/apple-touch-icon-114x114.png
similarity index 100%
rename from src/graiph-database/resources/browser/assets/images/device-icons/apple-touch-icon-114x114.png
rename to server/src/main/resources/browser/assets/images/device-icons/apple-touch-icon-114x114.png
diff --git a/src/graiph-database/resources/browser/assets/images/device-icons/apple-touch-icon-120x120.png b/server/src/main/resources/browser/assets/images/device-icons/apple-touch-icon-120x120.png
similarity index 100%
rename from src/graiph-database/resources/browser/assets/images/device-icons/apple-touch-icon-120x120.png
rename to server/src/main/resources/browser/assets/images/device-icons/apple-touch-icon-120x120.png
diff --git a/src/graiph-database/resources/browser/assets/images/device-icons/apple-touch-icon-144x144.png b/server/src/main/resources/browser/assets/images/device-icons/apple-touch-icon-144x144.png
similarity index 100%
rename from src/graiph-database/resources/browser/assets/images/device-icons/apple-touch-icon-144x144.png
rename to server/src/main/resources/browser/assets/images/device-icons/apple-touch-icon-144x144.png
diff --git a/src/graiph-database/resources/browser/assets/images/device-icons/apple-touch-icon-152x152.png b/server/src/main/resources/browser/assets/images/device-icons/apple-touch-icon-152x152.png
similarity index 100%
rename from src/graiph-database/resources/browser/assets/images/device-icons/apple-touch-icon-152x152.png
rename to server/src/main/resources/browser/assets/images/device-icons/apple-touch-icon-152x152.png
diff --git a/src/graiph-database/resources/browser/assets/images/device-icons/apple-touch-icon-180x180.png b/server/src/main/resources/browser/assets/images/device-icons/apple-touch-icon-180x180.png
similarity index 100%
rename from src/graiph-database/resources/browser/assets/images/device-icons/apple-touch-icon-180x180.png
rename to server/src/main/resources/browser/assets/images/device-icons/apple-touch-icon-180x180.png
diff --git a/src/graiph-database/resources/browser/assets/images/device-icons/apple-touch-icon-57x57.png b/server/src/main/resources/browser/assets/images/device-icons/apple-touch-icon-57x57.png
similarity index 100%
rename from src/graiph-database/resources/browser/assets/images/device-icons/apple-touch-icon-57x57.png
rename to server/src/main/resources/browser/assets/images/device-icons/apple-touch-icon-57x57.png
diff --git a/src/graiph-database/resources/browser/assets/images/device-icons/apple-touch-icon-60x60.png b/server/src/main/resources/browser/assets/images/device-icons/apple-touch-icon-60x60.png
similarity index 100%
rename from src/graiph-database/resources/browser/assets/images/device-icons/apple-touch-icon-60x60.png
rename to server/src/main/resources/browser/assets/images/device-icons/apple-touch-icon-60x60.png
diff --git a/src/graiph-database/resources/browser/assets/images/device-icons/apple-touch-icon-72x72.png b/server/src/main/resources/browser/assets/images/device-icons/apple-touch-icon-72x72.png
similarity index 100%
rename from src/graiph-database/resources/browser/assets/images/device-icons/apple-touch-icon-72x72.png
rename to server/src/main/resources/browser/assets/images/device-icons/apple-touch-icon-72x72.png
diff --git a/src/graiph-database/resources/browser/assets/images/device-icons/apple-touch-icon-76x76.png b/server/src/main/resources/browser/assets/images/device-icons/apple-touch-icon-76x76.png
similarity index 100%
rename from src/graiph-database/resources/browser/assets/images/device-icons/apple-touch-icon-76x76.png
rename to server/src/main/resources/browser/assets/images/device-icons/apple-touch-icon-76x76.png
diff --git a/src/graiph-database/resources/browser/assets/images/device-icons/apple-touch-icon-precomposed.png b/server/src/main/resources/browser/assets/images/device-icons/apple-touch-icon-precomposed.png
similarity index 100%
rename from src/graiph-database/resources/browser/assets/images/device-icons/apple-touch-icon-precomposed.png
rename to server/src/main/resources/browser/assets/images/device-icons/apple-touch-icon-precomposed.png
diff --git a/src/graiph-database/resources/browser/assets/images/device-icons/apple-touch-icon.png b/server/src/main/resources/browser/assets/images/device-icons/apple-touch-icon.png
similarity index 100%
rename from src/graiph-database/resources/browser/assets/images/device-icons/apple-touch-icon.png
rename to server/src/main/resources/browser/assets/images/device-icons/apple-touch-icon.png
diff --git a/src/graiph-database/resources/browser/assets/images/device-icons/browserconfig.xml b/server/src/main/resources/browser/assets/images/device-icons/browserconfig.xml
similarity index 100%
rename from src/graiph-database/resources/browser/assets/images/device-icons/browserconfig.xml
rename to server/src/main/resources/browser/assets/images/device-icons/browserconfig.xml
diff --git a/src/graiph-database/resources/browser/assets/images/device-icons/favicon-16x16.png b/server/src/main/resources/browser/assets/images/device-icons/favicon-16x16.png
similarity index 100%
rename from src/graiph-database/resources/browser/assets/images/device-icons/favicon-16x16.png
rename to server/src/main/resources/browser/assets/images/device-icons/favicon-16x16.png
diff --git a/src/graiph-database/resources/browser/assets/images/device-icons/favicon-32x32.png b/server/src/main/resources/browser/assets/images/device-icons/favicon-32x32.png
similarity index 100%
rename from src/graiph-database/resources/browser/assets/images/device-icons/favicon-32x32.png
rename to server/src/main/resources/browser/assets/images/device-icons/favicon-32x32.png
diff --git a/src/graiph-database/resources/browser/assets/images/device-icons/favicon-96x96.png b/server/src/main/resources/browser/assets/images/device-icons/favicon-96x96.png
similarity index 100%
rename from src/graiph-database/resources/browser/assets/images/device-icons/favicon-96x96.png
rename to server/src/main/resources/browser/assets/images/device-icons/favicon-96x96.png
diff --git a/src/graiph-database/resources/browser/assets/images/device-icons/favicon.ico b/server/src/main/resources/browser/assets/images/device-icons/favicon.ico
similarity index 100%
rename from src/graiph-database/resources/browser/assets/images/device-icons/favicon.ico
rename to server/src/main/resources/browser/assets/images/device-icons/favicon.ico
diff --git a/src/graiph-database/resources/browser/assets/images/device-icons/manifest.json b/server/src/main/resources/browser/assets/images/device-icons/manifest.json
similarity index 100%
rename from src/graiph-database/resources/browser/assets/images/device-icons/manifest.json
rename to server/src/main/resources/browser/assets/images/device-icons/manifest.json
diff --git a/src/graiph-database/resources/browser/assets/images/device-icons/mstile-144x144.png b/server/src/main/resources/browser/assets/images/device-icons/mstile-144x144.png
similarity index 100%
rename from src/graiph-database/resources/browser/assets/images/device-icons/mstile-144x144.png
rename to server/src/main/resources/browser/assets/images/device-icons/mstile-144x144.png
diff --git a/src/graiph-database/resources/browser/assets/images/device-icons/mstile-150x150.png b/server/src/main/resources/browser/assets/images/device-icons/mstile-150x150.png
similarity index 100%
rename from src/graiph-database/resources/browser/assets/images/device-icons/mstile-150x150.png
rename to server/src/main/resources/browser/assets/images/device-icons/mstile-150x150.png
diff --git a/src/graiph-database/resources/browser/assets/images/device-icons/mstile-310x150.png b/server/src/main/resources/browser/assets/images/device-icons/mstile-310x150.png
similarity index 100%
rename from src/graiph-database/resources/browser/assets/images/device-icons/mstile-310x150.png
rename to server/src/main/resources/browser/assets/images/device-icons/mstile-310x150.png
diff --git a/src/graiph-database/resources/browser/assets/images/device-icons/mstile-310x310.png b/server/src/main/resources/browser/assets/images/device-icons/mstile-310x310.png
similarity index 100%
rename from src/graiph-database/resources/browser/assets/images/device-icons/mstile-310x310.png
rename to server/src/main/resources/browser/assets/images/device-icons/mstile-310x310.png
diff --git a/src/graiph-database/resources/browser/assets/images/device-icons/mstile-70x70.png b/server/src/main/resources/browser/assets/images/device-icons/mstile-70x70.png
similarity index 100%
rename from src/graiph-database/resources/browser/assets/images/device-icons/mstile-70x70.png
rename to server/src/main/resources/browser/assets/images/device-icons/mstile-70x70.png
diff --git a/src/graiph-database/resources/browser/assets/images/device-icons/neo4j-desktop.svg b/server/src/main/resources/browser/assets/images/device-icons/neo4j-desktop.svg
similarity index 100%
rename from src/graiph-database/resources/browser/assets/images/device-icons/neo4j-desktop.svg
rename to server/src/main/resources/browser/assets/images/device-icons/neo4j-desktop.svg
diff --git a/src/graiph-database/resources/browser/assets/js/canvg/StackBlur.js b/server/src/main/resources/browser/assets/js/canvg/StackBlur.js
similarity index 100%
rename from src/graiph-database/resources/browser/assets/js/canvg/StackBlur.js
rename to server/src/main/resources/browser/assets/js/canvg/StackBlur.js
diff --git a/src/graiph-database/resources/browser/assets/js/canvg/canvg.js b/server/src/main/resources/browser/assets/js/canvg/canvg.js
similarity index 100%
rename from src/graiph-database/resources/browser/assets/js/canvg/canvg.js
rename to server/src/main/resources/browser/assets/js/canvg/canvg.js
diff --git a/src/graiph-database/resources/browser/assets/js/canvg/rgbcolor.js b/server/src/main/resources/browser/assets/js/canvg/rgbcolor.js
similarity index 100%
rename from src/graiph-database/resources/browser/assets/js/canvg/rgbcolor.js
rename to server/src/main/resources/browser/assets/js/canvg/rgbcolor.js
diff --git a/src/graiph-database/resources/browser/assets/labeled_node-0ae16d31f7f48dd8aad5adb0f2cc162d.png b/server/src/main/resources/browser/assets/labeled_node-0ae16d31f7f48dd8aad5adb0f2cc162d.png
similarity index 100%
rename from src/graiph-database/resources/browser/assets/labeled_node-0ae16d31f7f48dd8aad5adb0f2cc162d.png
rename to server/src/main/resources/browser/assets/labeled_node-0ae16d31f7f48dd8aad5adb0f2cc162d.png
diff --git a/src/graiph-database/resources/browser/assets/more_nodes-ffa75b2028a6ccc15ebca2ad38745ec8.png b/server/src/main/resources/browser/assets/more_nodes-ffa75b2028a6ccc15ebca2ad38745ec8.png
similarity index 100%
rename from src/graiph-database/resources/browser/assets/more_nodes-ffa75b2028a6ccc15ebca2ad38745ec8.png
rename to server/src/main/resources/browser/assets/more_nodes-ffa75b2028a6ccc15ebca2ad38745ec8.png
diff --git a/src/graiph-database/resources/browser/assets/neo4j-world-16a20d139c28611513ec675e56d16d41.png b/server/src/main/resources/browser/assets/neo4j-world-16a20d139c28611513ec675e56d16d41.png
similarity index 100%
rename from src/graiph-database/resources/browser/assets/neo4j-world-16a20d139c28611513ec675e56d16d41.png
rename to server/src/main/resources/browser/assets/neo4j-world-16a20d139c28611513ec675e56d16d41.png
diff --git a/src/graiph-database/resources/browser/assets/one_node-d6c52c9505f9fc7fe0d2d11f4eeb7387.png b/server/src/main/resources/browser/assets/one_node-d6c52c9505f9fc7fe0d2d11f4eeb7387.png
similarity index 100%
rename from src/graiph-database/resources/browser/assets/one_node-d6c52c9505f9fc7fe0d2d11f4eeb7387.png
rename to server/src/main/resources/browser/assets/one_node-d6c52c9505f9fc7fe0d2d11f4eeb7387.png
diff --git a/src/graiph-database/resources/browser/assets/order-graph-10ba26057f99c821a6bdaf4148acbc78.png b/server/src/main/resources/browser/assets/order-graph-10ba26057f99c821a6bdaf4148acbc78.png
similarity index 100%
rename from src/graiph-database/resources/browser/assets/order-graph-10ba26057f99c821a6bdaf4148acbc78.png
rename to server/src/main/resources/browser/assets/order-graph-10ba26057f99c821a6bdaf4148acbc78.png
diff --git a/src/graiph-database/resources/browser/assets/product-category-supplier-b5a40101da41fc85d63062c8cb4db224.png b/server/src/main/resources/browser/assets/product-category-supplier-b5a40101da41fc85d63062c8cb4db224.png
similarity index 100%
rename from src/graiph-database/resources/browser/assets/product-category-supplier-b5a40101da41fc85d63062c8cb4db224.png
rename to server/src/main/resources/browser/assets/product-category-supplier-b5a40101da41fc85d63062c8cb4db224.png
diff --git a/src/graiph-database/resources/browser/assets/product-graph-c8122b0ef629f6164833602056e3e577.png b/server/src/main/resources/browser/assets/product-graph-c8122b0ef629f6164833602056e3e577.png
similarity index 100%
rename from src/graiph-database/resources/browser/assets/product-graph-c8122b0ef629f6164833602056e3e577.png
rename to server/src/main/resources/browser/assets/product-graph-c8122b0ef629f6164833602056e3e577.png
diff --git a/src/graiph-database/resources/browser/assets/query-plan-1dbe2ddf9e7148d121cd60dc96f15389.svg b/server/src/main/resources/browser/assets/query-plan-1dbe2ddf9e7148d121cd60dc96f15389.svg
similarity index 100%
rename from src/graiph-database/resources/browser/assets/query-plan-1dbe2ddf9e7148d121cd60dc96f15389.svg
rename to server/src/main/resources/browser/assets/query-plan-1dbe2ddf9e7148d121cd60dc96f15389.svg
diff --git a/src/graiph-database/resources/browser/assets/query-plan-operator-cost-569d20fb755616dfa9c4b02bbff8e926.svg b/server/src/main/resources/browser/assets/query-plan-operator-cost-569d20fb755616dfa9c4b02bbff8e926.svg
similarity index 100%
rename from src/graiph-database/resources/browser/assets/query-plan-operator-cost-569d20fb755616dfa9c4b02bbff8e926.svg
rename to server/src/main/resources/browser/assets/query-plan-operator-cost-569d20fb755616dfa9c4b02bbff8e926.svg
diff --git a/src/graiph-database/resources/browser/assets/query-plan-operator-details-b61fa6e320448faf748ef91b3a2125d5.svg b/server/src/main/resources/browser/assets/query-plan-operator-details-b61fa6e320448faf748ef91b3a2125d5.svg
similarity index 100%
rename from src/graiph-database/resources/browser/assets/query-plan-operator-details-b61fa6e320448faf748ef91b3a2125d5.svg
rename to server/src/main/resources/browser/assets/query-plan-operator-details-b61fa6e320448faf748ef91b3a2125d5.svg
diff --git a/src/graiph-database/resources/browser/assets/query-plan-operator-rows-3755f84215d4c7e2b35aa8286a458fa9.svg b/server/src/main/resources/browser/assets/query-plan-operator-rows-3755f84215d4c7e2b35aa8286a458fa9.svg
similarity index 100%
rename from src/graiph-database/resources/browser/assets/query-plan-operator-rows-3755f84215d4c7e2b35aa8286a458fa9.svg
rename to server/src/main/resources/browser/assets/query-plan-operator-rows-3755f84215d4c7e2b35aa8286a458fa9.svg
diff --git a/src/graiph-database/resources/browser/assets/rel-props-f74e4cfe9d44f59af1c4c1eb228139b9.png b/server/src/main/resources/browser/assets/rel-props-f74e4cfe9d44f59af1c4c1eb228139b9.png
similarity index 100%
rename from src/graiph-database/resources/browser/assets/rel-props-f74e4cfe9d44f59af1c4c1eb228139b9.png
rename to server/src/main/resources/browser/assets/rel-props-f74e4cfe9d44f59af1c4c1eb228139b9.png
diff --git a/src/graiph-database/resources/browser/assets/relationships-3046fd08a53dc14b736c08ab0f518d7f.png b/server/src/main/resources/browser/assets/relationships-3046fd08a53dc14b736c08ab0f518d7f.png
similarity index 100%
rename from src/graiph-database/resources/browser/assets/relationships-3046fd08a53dc14b736c08ab0f518d7f.png
rename to server/src/main/resources/browser/assets/relationships-3046fd08a53dc14b736c08ab0f518d7f.png
diff --git a/src/graiph-database/resources/browser/assets/screen_code_frame-e2c698df376b5aa8cc07cd7f8ccae2ca.png b/server/src/main/resources/browser/assets/screen_code_frame-e2c698df376b5aa8cc07cd7f8ccae2ca.png
similarity index 100%
rename from src/graiph-database/resources/browser/assets/screen_code_frame-e2c698df376b5aa8cc07cd7f8ccae2ca.png
rename to server/src/main/resources/browser/assets/screen_code_frame-e2c698df376b5aa8cc07cd7f8ccae2ca.png
diff --git a/src/graiph-database/resources/browser/assets/screen_cypher_warn-a09604053edeb1b7d04adf56f1b3571b.png b/server/src/main/resources/browser/assets/screen_cypher_warn-a09604053edeb1b7d04adf56f1b3571b.png
similarity index 100%
rename from src/graiph-database/resources/browser/assets/screen_cypher_warn-a09604053edeb1b7d04adf56f1b3571b.png
rename to server/src/main/resources/browser/assets/screen_cypher_warn-a09604053edeb1b7d04adf56f1b3571b.png
diff --git a/src/graiph-database/resources/browser/assets/screen_editor-d6346b5cb91871943844584df77695ce.png b/server/src/main/resources/browser/assets/screen_editor-d6346b5cb91871943844584df77695ce.png
similarity index 100%
rename from src/graiph-database/resources/browser/assets/screen_editor-d6346b5cb91871943844584df77695ce.png
rename to server/src/main/resources/browser/assets/screen_editor-d6346b5cb91871943844584df77695ce.png
diff --git a/src/graiph-database/resources/browser/assets/screen_sidebar-045cbffb3261a8351cf29683f4742325.png b/server/src/main/resources/browser/assets/screen_sidebar-045cbffb3261a8351cf29683f4742325.png
similarity index 100%
rename from src/graiph-database/resources/browser/assets/screen_sidebar-045cbffb3261a8351cf29683f4742325.png
rename to server/src/main/resources/browser/assets/screen_sidebar-045cbffb3261a8351cf29683f4742325.png
diff --git a/src/graiph-database/resources/browser/assets/screen_stream-b84fff4144d58f194851153b9ddc9380.png b/server/src/main/resources/browser/assets/screen_stream-b84fff4144d58f194851153b9ddc9380.png
similarity index 100%
rename from src/graiph-database/resources/browser/assets/screen_stream-b84fff4144d58f194851153b9ddc9380.png
rename to server/src/main/resources/browser/assets/screen_stream-b84fff4144d58f194851153b9ddc9380.png
diff --git a/src/graiph-database/resources/browser/index.html b/server/src/main/resources/browser/index.html
similarity index 100%
rename from src/graiph-database/resources/browser/index.html
rename to server/src/main/resources/browser/index.html
diff --git a/src/graiph-database/resources/browser/main.chunkhash.bundle.js b/server/src/main/resources/browser/main.chunkhash.bundle.js
similarity index 100%
rename from src/graiph-database/resources/browser/main.chunkhash.bundle.js
rename to server/src/main/resources/browser/main.chunkhash.bundle.js
diff --git a/src/graiph-database/resources/browser/manifest.json b/server/src/main/resources/browser/manifest.json
similarity index 100%
rename from src/graiph-database/resources/browser/manifest.json
rename to server/src/main/resources/browser/manifest.json
diff --git a/src/graiph-database/resources/browser/sync-manager.chunkhash.bundle.js b/server/src/main/resources/browser/sync-manager.chunkhash.bundle.js
similarity index 100%
rename from src/graiph-database/resources/browser/sync-manager.chunkhash.bundle.js
rename to server/src/main/resources/browser/sync-manager.chunkhash.bundle.js
diff --git a/src/graiph-database/resources/browser/vendors~main.chunkhash.bundle.js b/server/src/main/resources/browser/vendors~main.chunkhash.bundle.js
similarity index 100%
rename from src/graiph-database/resources/browser/vendors~main.chunkhash.bundle.js
rename to server/src/main/resources/browser/vendors~main.chunkhash.bundle.js
diff --git a/server/src/main/resources/logo.txt b/server/src/main/resources/logo.txt
new file mode 100644
index 00000000..19db1af4
--- /dev/null
+++ b/server/src/main/resources/logo.txt
@@ -0,0 +1,9 @@
+
+ ______ _ _____ ______
+(_____ \ | | (____ \ (____ \
+ _____) )___ ____ _ | | ____ _ \ \ ____) )
+| ____/ _ | _ \ / || |/ _ | | | | __ (
+| | ( ( | | | | ( (_| ( ( | | |__/ /| |__) )
+|_| \_||_|_| |_|\____|\_||_|_____/ |______/
+
+PandaDB Node Server (ver 0.0.2.20200320)
diff --git a/server/src/main/scala/cn/pandadb/server/ClusterLog.scala b/server/src/main/scala/cn/pandadb/server/ClusterLog.scala
new file mode 100644
index 00000000..4d8f9501
--- /dev/null
+++ b/server/src/main/scala/cn/pandadb/server/ClusterLog.scala
@@ -0,0 +1,79 @@
+package cn.pandadb.server
+
+import java.io._
+
+import com.google.gson.Gson
+
+import scala.io.Source
+
+/**
+ * @Author: Airzihao
+ * @Description:
+ * @Date: Created at 13:10 2019/11/30
+ * @Modified By:
+ */
+
+case class DataLogDetail(val versionNum: Int, val command: String) {
+
+}
+
+trait DataLogWriter {
+ def write(row: DataLogDetail): Unit;
+ def getLastVersion(): Int;
+}
+
+trait DataLogReader {
+ def consume[T](consumer: (DataLogDetail) => T, sinceVersion: Int = -1): Iterable[T];
+ def getLastVersion(): Int;
+}
+
+object JsonDataLogRW {
+ def open(file: File): JsonDataLogRW = {
+ if (!file.exists) {
+ file.getParentFile.mkdirs()
+ file.createNewFile()
+ }
+ new JsonDataLogRW(file)
+ }
+}
+
+class JsonDataLogRW(logFile: File) extends DataLogWriter with DataLogReader {
+
+ val logFIleIter: Iterator[String] = Source.fromFile(logFile).getLines()
+
+ val _gson = new Gson()
+
+ private var lastVersion: Int = {
+ if (logFile.length() == 0) {
+ -1
+ } else {
+ var _tempVersion = -1
+ val _iter = Source.fromFile(logFile).getLines()
+ _iter.foreach(line => {
+ val _lineSerialNum = _gson.fromJson(line, new DataLogDetail(0, "").getClass).versionNum
+ if(_lineSerialNum > _tempVersion) {
+ _tempVersion = _lineSerialNum
+ }
+ })
+ _tempVersion
+ }
+ }
+
+ override def consume[T](consumer: DataLogDetail => T, sinceVersion: Int): Iterable[T] = {
+ logFIleIter.toIterable.map(line => _gson.fromJson(line, new DataLogDetail(0, "").getClass))
+ .filter(dataLogDetail => dataLogDetail.versionNum > sinceVersion)
+ .map(consumer(_))
+ }
+
+ override def write(row: DataLogDetail): Unit = {
+ lastVersion += 1
+ val _fileAppender = new FileWriter(logFile, true)
+ _fileAppender.append(s"${_gson.toJson(row)}\n");
+ _fileAppender.flush()
+ _fileAppender.close()
+ }
+
+ override def getLastVersion(): Int = {
+ lastVersion
+ }
+}
diff --git a/server/src/main/scala/cn/pandadb/server/DataVersionRecovery.scala b/server/src/main/scala/cn/pandadb/server/DataVersionRecovery.scala
new file mode 100644
index 00000000..342ecb94
--- /dev/null
+++ b/server/src/main/scala/cn/pandadb/server/DataVersionRecovery.scala
@@ -0,0 +1,43 @@
+package cn.pandadb.server
+
+import java.io.File
+
+import cn.pandadb.network.NodeAddress
+import org.neo4j.driver.GraphDatabase
+
+/**
+ * @Author: Airzihao
+ * @Description:
+ * @Date: Created in 23:06 2019/12/2
+ * @Modified By:
+ */
+
+case class DataVersionRecoveryArgs(val localLogFile: File, val clusterLogFile: File,
+ val localNodeAddress: NodeAddress)
+
+class LocalDataVersionRecovery(args: DataVersionRecoveryArgs) {
+ val localLog = new JsonDataLogRW(args.localLogFile)
+ val sinceVersion: Int = localLog.getLastVersion()
+ val clusterLog = new JsonDataLogRW(args.clusterLogFile)
+ val clusterVersion: Int = clusterLog.getLastVersion()
+
+ private def _collectCypherList(): List[String] = {
+ clusterLog.consume(logItem => logItem.command, sinceVersion).toList
+ }
+
+ def updateLocalVersion(): Unit = {
+ if (clusterVersion > sinceVersion) {
+ val cypherList = _collectCypherList()
+ val boltURI = s"bolt://" + args.localNodeAddress.getAsString
+ val driver = GraphDatabase.driver(boltURI)
+ val session = driver.session()
+ cypherList.foreach(cypher => {
+ val _tx = session.beginTransaction()
+ _tx.run(cypher)
+ _tx.success()
+ _tx.close()
+ })
+ session.close()
+ }
+ }
+}
\ No newline at end of file
diff --git a/server/src/main/scala/cn/pandadb/server/MasterRole.scala b/server/src/main/scala/cn/pandadb/server/MasterRole.scala
new file mode 100644
index 00000000..ce6b6de2
--- /dev/null
+++ b/server/src/main/scala/cn/pandadb/server/MasterRole.scala
@@ -0,0 +1,142 @@
+package cn.pandadb.server
+
+import cn.pandadb.network._
+import org.apache.zookeeper.{CreateMode, ZooDefs}
+import org.neo4j.driver._
+
+import scala.collection.mutable.ListBuffer
+import scala.concurrent.{Await, Future}
+import scala.concurrent.duration._
+import scala.concurrent.ExecutionContext.Implicits.global
+
+/**
+ * @Author: Airzihao
+ * @Description: This class is instanced when a node is selected as the master node.
+ * @Date: Created at 13:13 2019/11/27
+ * @Modified By:
+ */
+
+trait Master {
+
+ var allNodes: Iterable[NodeAddress]
+
+ val clusterClient: ClusterClient
+
+ var globalWriteLock: NaiveLock
+
+ var globalReadLock: NaiveLock
+
+ var listenerList: List[ZKClusterEventListener]
+
+ def addListener(listener: ZKClusterEventListener)
+
+ def clusterWrite(cypher: String)
+}
+
+class MasterRole(zkClusterClient: ZookeeperBasedClusterClient, localAddress: NodeAddress) extends Master {
+
+ val localNodeAddress = localAddress
+ override var listenerList: List[ZKClusterEventListener] = _
+
+ // how to init it?
+ private var currentState: ClusterState = new ClusterState {}
+ override val clusterClient = zkClusterClient
+ val masterNodeAddress = localAddress.getAsString
+ override var allNodes: Iterable[NodeAddress] = clusterClient.getAllNodes()
+ override var globalReadLock: NaiveLock = new NaiveReadLock(clusterClient)
+ override var globalWriteLock: NaiveLock = new NaiveWriteLock(clusterClient)
+
+ private def initWriteContext(): Unit = {
+ globalReadLock = new NaiveReadLock(clusterClient)
+ globalWriteLock = new NaiveWriteLock(clusterClient)
+ }
+
+ def setClusterState(state: ClusterState): Unit = {
+ currentState = state
+ }
+
+ private def distributeWriteStatement(cypher: String): Unit = {
+ var tempResult: StatementResult = null
+ var futureTasks = new ListBuffer[Future[Boolean]]
+ for (nodeAddress <- allNodes) {
+ if (nodeAddress.getAsString != masterNodeAddress) {
+ val future = Future[Boolean] {
+ try {
+ val uri = s"bolt://" + nodeAddress.getAsString
+ val driver = GraphDatabase.driver(uri,
+ AuthTokens.basic("", ""))
+ val session = driver.session()
+ val tx = session.beginTransaction()
+ tempResult = tx.run(cypher)
+ tx.success()
+ tx.close()
+ session.close()
+ true
+ } catch {
+ case e: Exception =>
+ throw new Exception("Write-cluster operation failed.")
+ false
+ }
+ }
+ futureTasks.append(future)
+ }
+ }
+ futureTasks.foreach(future => Await.result(future, 3.seconds))
+ }
+
+ // TODO finetune the state change mechanism
+ override def clusterWrite(cypher: String): Unit = {
+ val preVersion = zkClusterClient.getClusterDataVersion()
+ initWriteContext()
+ setClusterState(new Writing)
+ allNodes = clusterClient.getAllNodes()
+ globalWriteLock.lock()
+ // key func
+ distributeWriteStatement(cypher)
+ globalWriteLock.unlock()
+ setClusterState(new Finished)
+ setClusterState(new UnlockedServing)
+ val curVersion = preVersion + 1
+ _setDataVersion(curVersion)
+ }
+
+ def clusterRead(cypher: String): StatementResult = {
+ val iter = allNodes.iterator
+ var statementResult: StatementResult = null;
+ while (iter.hasNext) {
+ val str = iter.next().getAsString
+ if( str != masterNodeAddress) {
+ val uri = s"bolt://" + str
+ val driver = GraphDatabase.driver(uri)
+ statementResult = driver.session().run(cypher)
+ }
+ }
+ statementResult
+ }
+
+ override def addListener(listener: ZKClusterEventListener): Unit = {
+ listenerList = listener :: listenerList
+ }
+
+ private def _setDataVersion(curVersion: Int): Unit = {
+ _updateFreshNode()
+ clusterClient.curator.setData().forPath(ZKPathConfig.dataVersionPath, BytesTransform.serialize(curVersion))
+ }
+
+ private def _updateFreshNode(): Unit = {
+ val children = clusterClient.curator.getChildren.forPath(ZKPathConfig.freshNodePath)
+ // delete old node
+ if(children.isEmpty == false) {
+ val child = children.iterator()
+ while (child.hasNext) {
+ val fullPath = ZKPathConfig.freshNodePath + "/" + child.next()
+ clusterClient.curator.delete().forPath(fullPath)
+ }
+ }
+ val curFreshNodeRpc = MainServerContext.nodeAddress.getAsString
+ clusterClient.curator.create().creatingParentsIfNeeded()
+ .withMode(CreateMode.PERSISTENT)
+ .withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE)
+ .forPath(ZKPathConfig.freshNodePath + s"/" + curFreshNodeRpc)
+ }
+}
\ No newline at end of file
diff --git a/server/src/main/scala/cn/pandadb/server/NaiveLock.scala b/server/src/main/scala/cn/pandadb/server/NaiveLock.scala
new file mode 100644
index 00000000..bcb9001e
--- /dev/null
+++ b/server/src/main/scala/cn/pandadb/server/NaiveLock.scala
@@ -0,0 +1,74 @@
+package cn.pandadb.server
+
+import cn.pandadb.network.{NodeAddress, ZookeeperBasedClusterClient}
+
+/**
+ * @Author: Airzihao
+ * @Description:
+ * @Date: Created in 23:28 2019/11/27
+ * @Modified By:
+ */
+trait NaiveLock {
+
+ def lock()
+ def unlock()
+}
+
+class NaiveWriteLock(clusterClient: ZookeeperBasedClusterClient) extends NaiveLock {
+
+ val allNodes = clusterClient.getAllNodes()
+ var nodeList = allNodes.toList
+ val masterNodeAddress: NodeAddress = clusterClient.getWriteMasterNode("").get
+ val register = new ZKServiceRegistry(clusterClient.zkServerAddress)
+
+ override def lock(): Unit = {
+ while (nodeList.length == 0) {
+ Thread.sleep(1000)
+ nodeList = clusterClient.getAllNodes().toList
+ }
+ nodeList.foreach(lockOrdinaryNode(_))
+ lockLeaderNode(masterNodeAddress)
+ }
+
+ override def unlock(): Unit = {
+ nodeList.foreach(unlockOrdinaryNode(_))
+ unlockLeaderNode(masterNodeAddress)
+ }
+
+ def lockOrdinaryNode(node: NodeAddress): Unit = {
+ register.unRegisterOrdinaryNode(node)
+ }
+
+ def lockLeaderNode(node: NodeAddress): Unit = {
+ register.unRegisterLeaderNode(node)
+ }
+
+ def unlockOrdinaryNode(node: NodeAddress): Unit = {
+ register.registerAsOrdinaryNode(node)
+ }
+ def unlockLeaderNode(node: NodeAddress): Unit = {
+ register.registerAsLeader(node)
+ }
+}
+
+class NaiveReadLock(clusterClient: ZookeeperBasedClusterClient) extends NaiveLock {
+
+ val register = new ZKServiceRegistry(clusterClient.zkServerAddress)
+ var masterNodeAddress: NodeAddress = _
+
+ override def lock(): Unit = {
+ masterNodeAddress = clusterClient.getWriteMasterNode("").get
+ lockLeaderNode(masterNodeAddress)
+ }
+
+ override def unlock(): Unit = {
+ unlockLeaderNode(masterNodeAddress)
+ }
+
+ def lockLeaderNode(node: NodeAddress): Unit = {
+ register.unRegisterLeaderNode(node)
+ }
+ def unlockLeaderNode(node: NodeAddress): Unit = {
+ register.registerAsLeader(node)
+ }
+}
diff --git a/server/src/main/scala/cn/pandadb/server/PNodeServer.scala b/server/src/main/scala/cn/pandadb/server/PNodeServer.scala
new file mode 100644
index 00000000..fe40df24
--- /dev/null
+++ b/server/src/main/scala/cn/pandadb/server/PNodeServer.scala
@@ -0,0 +1,149 @@
+package cn.pandadb.server
+
+import java.io.{File, FileInputStream}
+import java.util.concurrent.CountDownLatch
+import java.util.{Optional, Properties}
+
+import cn.pandadb.blob.BlobStorageModule
+import cn.pandadb.cypherplus.CypherPlusModule
+import cn.pandadb.externalprops.ExternalPropertiesModule
+import cn.pandadb.network.{NodeAddress, ZKPathConfig, ZookeeperBasedClusterClient}
+import cn.pandadb.server.internode.InterNodeRequestHandler
+import cn.pandadb.server.neo4j.Neo4jRequestHandler
+import cn.pandadb.server.rpc.{NettyRpcServer, PNodeRpcClient}
+import cn.pandadb.util._
+import org.apache.commons.io.IOUtils
+import org.apache.curator.framework.CuratorFramework
+import org.apache.curator.framework.recipes.leader.{LeaderSelector, LeaderSelectorListenerAdapter}
+import org.neo4j.driver.GraphDatabase
+import org.neo4j.server.CommunityBootstrapper
+
+import scala.collection.JavaConversions
+
+/**
+ * Created by bluejoe on 2019/7/17.
+ */
+object PNodeServer extends Logging {
+ val logo = IOUtils.toString(this.getClass.getClassLoader.getResourceAsStream("logo.txt"), "utf-8");
+
+ def startServer(dbDir: File, configFile: File, overrided: Map[String, String] = Map()): PNodeServer = {
+ val props = new Properties()
+ props.load(new FileInputStream(configFile))
+ val server = new PNodeServer(dbDir, JavaConversions.propertiesAsScalaMap(props).toMap ++ overrided);
+ server.start();
+ server;
+ }
+}
+
+class PNodeServer(dbDir: File, props: Map[String, String])
+ extends LeaderSelectorListenerAdapter with Logging {
+ //TODO: we will replace neo4jServer with InterNodeRpcServer someday!!
+ val neo4jServer = new CommunityBootstrapper();
+ val runningLock = new CountDownLatch(1)
+
+ val modules = new PandaModules();
+ val context = new ContextMap();
+
+ val config = new Configuration() {
+ override def getRaw(name: String): Option[String] = props.get(name)
+ }
+
+ val pmc = PandaModuleContext(config, dbDir, context);
+
+ modules.add(new MainServerModule())
+ .add(new BlobStorageModule())
+ .add(new ExternalPropertiesModule())
+ .add(new CypherPlusModule())
+
+ modules.init(pmc);
+
+ val np = MainServerContext.nodeAddress
+
+ val serverKernel = new NettyRpcServer("0.0.0.0", MainServerContext.rpcPort, "PNodeRpc-service");
+ serverKernel.accept(Neo4jRequestHandler());
+ serverKernel.accept(InterNodeRequestHandler());
+
+ val dataLogRW = JsonDataLogRW.open(new File(dbDir, "dataVersionLog.json"))
+
+ MainServerContext.bindDataLogReaderWriter(dataLogRW, dataLogRW)
+ val clusterClient: ZookeeperBasedClusterClient = new ZookeeperBasedClusterClient(MainServerContext.zkServerAddressStr)
+
+ def start(): Unit = {
+ Runtime.getRuntime().addShutdownHook(new Thread() {
+ override def run(): Unit = {
+ shutdown();
+ }
+ });
+
+ neo4jServer.start(dbDir, Optional.empty(),
+ JavaConversions.mapAsJavaMap(props + ("dbms.connector.bolt.listen_address" -> np.getAsString)));
+
+ modules.start(pmc);
+ //FIXME: watch dog is not a PNode
+ if (!GlobalContext.isWatchDog()) {
+ serverKernel.start({
+ //scalastyle:off
+ println(PNodeServer.logo);
+
+ if (_isUpToDate() == false) {
+ _updateLocalData()
+ }
+ _joinInLeaderSelection()
+ new ZKServiceRegistry(MainServerContext.zkServerAddressStr).registerAsOrdinaryNode(np)
+ })
+ }
+ }
+
+ def shutdown(): Unit = {
+ modules.close(pmc);
+ runningLock.countDown()
+ serverKernel.shutdown();
+ }
+
+ override def takeLeadership(curatorFramework: CuratorFramework): Unit = {
+
+ new ZKServiceRegistry(MainServerContext.zkServerAddressStr).registerAsLeader(np)
+ val masterRole = new MasterRole(clusterClient, np)
+ MainServerContext.bindMasterRole(masterRole)
+
+ logger.debug(s"taken leader ship...");
+ //yes, i won't quit, never!
+ runningLock.await()
+ logger.debug(s"shutdown...");
+ }
+
+ private def _joinInLeaderSelection(): Unit = {
+ val leaderSelector = new LeaderSelector(clusterClient.curator, ZKPathConfig.registryPath + "/_leader", this);
+ leaderSelector.start();
+ }
+
+ private def _isUpToDate(): Boolean = {
+ dataLogRW.getLastVersion() == clusterClient.getClusterDataVersion()
+ }
+
+ //FIXME: updata->update
+ private def _updateLocalData(): Unit = {
+ // if can't get now, wait here.
+ val cypherArray = _getRemoteLogs()
+
+ val localDriver = GraphDatabase.driver(s"bolt://${np.getAsString}")
+ val session = localDriver.session()
+ cypherArray.foreach(logItem => {
+ val tx = session.beginTransaction()
+ try {
+ val localPreVersion = dataLogRW.getLastVersion()
+ tx.run(logItem.command)
+ tx.success()
+ tx.close()
+ dataLogRW.write(logItem)
+ }
+ })
+ }
+
+ // todo: Iterable[]
+ private def _getRemoteLogs(): Iterable[DataLogDetail] = {
+ val lastFreshNodeIP = clusterClient.getFreshNodeIp()
+ val rpcClient = PNodeRpcClient.connect(NodeAddress.fromString(lastFreshNodeIP))
+ rpcClient.getRemoteLogs(dataLogRW.getLastVersion())
+ }
+}
\ No newline at end of file
diff --git a/server/src/main/scala/cn/pandadb/server/ServiceRegistry.scala b/server/src/main/scala/cn/pandadb/server/ServiceRegistry.scala
new file mode 100644
index 00000000..0a4269ee
--- /dev/null
+++ b/server/src/main/scala/cn/pandadb/server/ServiceRegistry.scala
@@ -0,0 +1,88 @@
+package cn.pandadb.server
+
+import cn.pandadb.network.{NodeAddress, ZKPathConfig}
+import org.apache.curator.framework.{CuratorFramework, CuratorFrameworkFactory}
+import org.apache.curator.retry.ExponentialBackoffRetry
+import org.apache.zookeeper.{CreateMode, ZooDefs}
+
+trait ServiceRegistry {
+
+ def registry(servicePath: String, localNodeAddress: String)
+}
+
+class ZKServiceRegistry(zkString: String) extends ServiceRegistry {
+
+ var localNodeAddress: String = _
+ val zkServerAddress = zkString
+ val curator: CuratorFramework = CuratorFrameworkFactory.newClient(zkServerAddress,
+ new ExponentialBackoffRetry(1000, 3));
+ curator.start()
+
+ def registry(servicePath: String, localNodeAddress: String): Unit = {
+ val registryPath = ZKPathConfig.registryPath
+ val nodeAddress = servicePath + s"/" + localNodeAddress
+ /*
+ * node mode in zk:
+ * pandaDB
+ * / | \
+ * / | \
+ * ordinaryNodes leader data
+ * / | \
+ * addresses leaderAddress version(not implemented)
+ *
+ */
+
+ // Create registry node (pandanode, persistent)
+ if(curator.checkExists().forPath(registryPath) == null) {
+ curator.create()
+ .creatingParentsIfNeeded()
+ .withMode(CreateMode.PERSISTENT)
+ .withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE)
+ .forPath(registryPath)
+ }
+
+ // Create service node (persistent)
+ if(curator.checkExists().forPath(servicePath) == null) {
+ curator.create()
+ .creatingParentsIfNeeded()
+ .withMode(CreateMode.PERSISTENT)
+ .withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE)
+ .forPath(servicePath)
+ }
+
+ // Create address node (temp)
+ if(curator.checkExists().forPath(nodeAddress) == null) {
+ curator.create()
+ .creatingParentsIfNeeded()
+ .withMode(CreateMode.EPHEMERAL)
+ .withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE)
+ .forPath(nodeAddress)
+ }
+ }
+
+ def registerAsOrdinaryNode(nodeAddress: NodeAddress): Unit = {
+ localNodeAddress = nodeAddress.getAsString
+ registry(ZKPathConfig.ordinaryNodesPath, localNodeAddress)
+ }
+
+ def registerAsLeader(nodeAddress: NodeAddress): Unit = {
+ localNodeAddress = nodeAddress.getAsString
+ registry(ZKPathConfig.leaderNodePath, localNodeAddress)
+ }
+
+ def unRegisterOrdinaryNode(node: NodeAddress): Unit = {
+ val nodeAddress = node.getAsString
+ val ordinaryNodePath = ZKPathConfig.ordinaryNodesPath + s"/" + nodeAddress
+ if(curator.checkExists().forPath(ordinaryNodePath) != null) {
+ curator.delete().forPath(ordinaryNodePath)
+ }
+ }
+
+ def unRegisterLeaderNode(node: NodeAddress): Unit = {
+ val nodeAddress = node.getAsString
+ val leaderNodePath = ZKPathConfig.leaderNodePath + s"/" + nodeAddress
+ if(curator.checkExists().forPath(leaderNodePath) != null) {
+ curator.delete().forPath(leaderNodePath)
+ }
+ }
+}
diff --git a/server/src/main/scala/cn/pandadb/server/internode/PNodeStatementProcessor.scala b/server/src/main/scala/cn/pandadb/server/internode/PNodeStatementProcessor.scala
new file mode 100644
index 00000000..ec72b14f
--- /dev/null
+++ b/server/src/main/scala/cn/pandadb/server/internode/PNodeStatementProcessor.scala
@@ -0,0 +1,88 @@
+package cn.pandadb.server.internode
+
+import java.time.Duration
+import java.util
+
+import cn.pandadb.cypherplus.utils.CypherPlusUtils
+import cn.pandadb.server.{MainServerContext, DataLogDetail}
+import cn.pandadb.util.GlobalContext
+import org.neo4j.bolt.runtime.{BoltResult, StatementMetadata, StatementProcessor, TransactionStateMachineSPI}
+import org.neo4j.bolt.v1.runtime.bookmarking.Bookmark
+import org.neo4j.function.{ThrowingBiConsumer, ThrowingConsumer}
+import org.neo4j.kernel.impl.util.ValueUtils
+import org.neo4j.values.AnyValue
+import org.neo4j.values.virtual.MapValue
+
+import scala.collection.{JavaConversions, mutable}
+
+/**
+ * Created by bluejoe on 2019/11/4.
+ */
+class PNodeStatementProcessor(source: StatementProcessor, spi: TransactionStateMachineSPI) extends StatementProcessor {
+
+ override def markCurrentTransactionForTermination(): Unit = source.markCurrentTransactionForTermination()
+
+ override def commitTransaction(): Bookmark = source.commitTransaction()
+
+ override def run(statement: String, params: MapValue): StatementMetadata = source.run(statement, params)
+
+ override def run(statement: String, params: MapValue, bookmark: Bookmark, txTimeout: Duration,
+ txMetaData: util.Map[String, AnyRef]): StatementMetadata = {
+
+ // param transformation, contribute by codeBabyLin
+ val paramMap = new mutable.HashMap[String, AnyRef]()
+ val myConsumer = new ThrowingBiConsumer[String, AnyValue, Exception]() {
+ override def accept(var1: String, var2: AnyValue): Unit = {
+ val key = var1
+ val value = ValueUtils.asValue(var2).asObject()
+ paramMap.update(key, value)
+ }
+ }
+ params.foreach(myConsumer)
+ val mapTrans = JavaConversions.mapAsJavaMap(paramMap)
+
+ //pickup a runnable node
+ if (CypherPlusUtils.isWriteStatement(statement)) {
+ if (GlobalContext.isLeaderNode) {
+ val masterRole = MainServerContext.masterRole
+ masterRole.clusterWrite(statement)
+ }
+ val metaData = source.run(statement, params)
+ val curVersion = _getLocalDataVersion() + 1
+ _writeDataLog(curVersion, statement)
+ metaData
+ } else {
+ source.run(statement, params)
+ }
+ }
+
+ private def _getLocalDataVersion(): Int = {
+ MainServerContext.dataLogWriter.getLastVersion
+ }
+
+ // pandaDB
+ private def _writeDataLog(curVersion: Int, cypher: String): Unit = {
+ val logItem = new DataLogDetail(curVersion, cypher)
+ MainServerContext.dataLogWriter.write(logItem)
+ }
+
+ override def streamResult(resultConsumer: ThrowingConsumer[BoltResult, Exception]): Bookmark = {
+ source.streamResult(resultConsumer)
+ }
+
+ override def hasOpenStatement: Boolean = source.hasOpenStatement
+
+ override def rollbackTransaction(): Unit = source.rollbackTransaction()
+
+ override def hasTransaction: Boolean = source.hasTransaction
+
+ override def reset(): Unit = source.reset()
+
+ override def validateTransaction(): Unit = source.validateTransaction()
+
+ override def beginTransaction(bookmark: Bookmark): Unit = source.beginTransaction(bookmark)
+
+ override def beginTransaction(bookmark: Bookmark, txTimeout: Duration, txMetadata: util.Map[String, AnyRef]): Unit =
+ source.beginTransaction(bookmark, txTimeout, txMetadata)
+
+}
\ No newline at end of file
diff --git a/server/src/main/scala/cn/pandadb/server/internode/internode.scala b/server/src/main/scala/cn/pandadb/server/internode/internode.scala
new file mode 100644
index 00000000..f8eeb225
--- /dev/null
+++ b/server/src/main/scala/cn/pandadb/server/internode/internode.scala
@@ -0,0 +1,23 @@
+package cn.pandadb.server.internode
+
+import cn.pandadb.network.internal.message.{InternalRpcRequest, InternalRpcResponse}
+import cn.pandadb.server.rpc.RequestHandler
+import cn.pandadb.server.{DataLogDetail, MainServerContext}
+
+/**
+ * Created by bluejoe on 2019/11/25.
+ */
+case class InterNodeRequestHandler() extends RequestHandler {
+ override val logic: PartialFunction[InternalRpcRequest, InternalRpcResponse] = {
+ case GetLogDetailsRequest(sinceVersion: Int) =>
+ GetLogDetailsResponse(MainServerContext.dataLogReader.consume(logItem => logItem, sinceVersion))
+ }
+}
+
+case class GetLogDetailsRequest(sinceVersion: Int) extends InternalRpcRequest {
+
+}
+
+case class GetLogDetailsResponse(logs: Iterable[DataLogDetail]) extends InternalRpcResponse {
+
+}
\ No newline at end of file
diff --git a/server/src/main/scala/cn/pandadb/server/module.scala b/server/src/main/scala/cn/pandadb/server/module.scala
new file mode 100644
index 00000000..3869cd3e
--- /dev/null
+++ b/server/src/main/scala/cn/pandadb/server/module.scala
@@ -0,0 +1,55 @@
+package cn.pandadb.server
+
+import cn.pandadb.network.{ClusterClient, NodeAddress, ZKPathConfig}
+import cn.pandadb.util._
+
+class MainServerModule extends PandaModule {
+ override def init(ctx: PandaModuleContext): Unit = {
+ val conf = ctx.configuration;
+ import ConfigUtils._
+ MainServerContext.bindNodeAddress(NodeAddress.fromString(conf.getRequiredValueAsString("node.server.address")));
+ MainServerContext.bindZKServerAddressStr(conf.getRequiredValueAsString("zookeeper.address"))
+ MainServerContext.bingRpcPort(conf.getRequiredValueAsInt("rpc.port"))
+ ZKPathConfig.initZKPath(MainServerContext.zkServerAddressStr)
+ }
+
+ override def close(ctx: PandaModuleContext): Unit = {
+
+ }
+
+ override def start(ctx: PandaModuleContext): Unit = {
+
+ }
+}
+
+object MainServerContext extends ContextMap {
+ def bindMasterRole(role: MasterRole): Unit = {
+ GlobalContext.setLeaderNode(true)
+ super.put[MasterRole](role);
+ }
+
+ def bindDataLogReaderWriter(logReader: DataLogReader, logWriter: DataLogWriter): Unit = {
+ super.put[DataLogReader](logReader)
+ super.put[DataLogWriter](logWriter)
+ }
+
+ def bindZKServerAddressStr(zkAddressString: String): Unit = put("zookeeper.address", zkAddressString)
+
+ def dataLogWriter: DataLogWriter = super.get[DataLogWriter]
+
+ def dataLogReader: DataLogReader = super.get[DataLogReader]
+
+ def bindNodeAddress(nodeAddress: NodeAddress): Unit = put("node.server.address", nodeAddress);
+
+ def bingRpcPort(port: Int): Unit = put("rpcPort", port)
+
+ def rpcPort: Int = get("rpcPort")
+
+ def nodeAddress: NodeAddress = get("node.server.address");
+
+ def zkServerAddressStr: String = get("zookeeper.address");
+
+ def masterRole: MasterRole = super.get[MasterRole]
+
+ def clusterClient: ClusterClient = super.get[ClusterClient]
+}
\ No newline at end of file
diff --git a/server/src/main/scala/cn/pandadb/server/neo4j/Neo4jAgent.scala b/server/src/main/scala/cn/pandadb/server/neo4j/Neo4jAgent.scala
new file mode 100644
index 00000000..1f742db8
--- /dev/null
+++ b/server/src/main/scala/cn/pandadb/server/neo4j/Neo4jAgent.scala
@@ -0,0 +1,39 @@
+package cn.pandadb.server.neo4j
+
+import cn.pandadb.network.internal.message.{InternalRpcRequest, InternalRpcResponse}
+import cn.pandadb.server.rpc.RequestHandler
+
+/**
+ * Created by bluejoe on 2019/11/25.
+ */
+case class Neo4jRequestHandler() extends RequestHandler {
+ override val logic: PartialFunction[InternalRpcRequest, InternalRpcResponse] = {
+ //example code
+ case RunCommandRequest(command: String) =>
+ RunCommandResponse(Array())
+ }
+}
+
+case class RunCommandRequest(command: String) extends InternalRpcRequest {
+
+}
+
+case class RunCommandResponse(results: Array[Result]) extends InternalRpcResponse {
+
+}
+
+case class BeginTransactionRequest() extends InternalRpcRequest {
+
+}
+
+case class CloseTransactionRequest() extends InternalRpcRequest {
+
+}
+
+class Result {
+
+}
+
+case class ServerSideExceptionResponse(msg: String) extends InternalRpcResponse {
+
+}
\ No newline at end of file
diff --git a/server/src/main/scala/cn/pandadb/server/rpc/NettyRpcClient.scala b/server/src/main/scala/cn/pandadb/server/rpc/NettyRpcClient.scala
new file mode 100644
index 00000000..2fed8558
--- /dev/null
+++ b/server/src/main/scala/cn/pandadb/server/rpc/NettyRpcClient.scala
@@ -0,0 +1,48 @@
+package cn.pandadb.server.rpc
+
+import cn.pandadb.network.NodeAddress
+import cn.pandadb.server.DataLogDetail
+import cn.pandadb.server.internode.{GetLogDetailsRequest, GetLogDetailsResponse}
+import cn.pandadb.util.Logging
+import net.neoremind.kraps.RpcConf
+import net.neoremind.kraps.rpc.{RpcAddress, RpcEnv, RpcEnvClientConfig}
+import net.neoremind.kraps.rpc.netty.NettyRpcEnvFactory
+
+import scala.concurrent.duration.Duration
+import scala.concurrent.Await
+
+object PNodeRpcClient {
+
+ // if can't connect, wait for it
+ def connect(remoteAddress: NodeAddress): PNodeRpcClient = {
+ try {
+ new PNodeRpcClient(remoteAddress)
+ }
+ catch {
+ case e: Exception =>
+ Thread.sleep(2000)
+ connect(remoteAddress)
+ }
+ }
+}
+
+case class PNodeRpcClient(val remoteAddress: NodeAddress) extends Logging {
+
+ val rpcEnv: RpcEnv = {
+ val rpcConf = new RpcConf()
+ val config = RpcEnvClientConfig(rpcConf, "PNodeRpc-client")
+ NettyRpcEnvFactory.create(config)
+ }
+
+ val endPointRef = rpcEnv.setupEndpointRef(RpcAddress(remoteAddress.host, remoteAddress.port), "PNodeRpc-service")
+
+ def close(): Unit = {
+ rpcEnv.stop(endPointRef)
+ }
+
+ def getRemoteLogs(sinceVersion: Int): Iterable[DataLogDetail] = {
+ val response: GetLogDetailsResponse = Await.result(endPointRef.ask[GetLogDetailsResponse](GetLogDetailsRequest(sinceVersion)), Duration.Inf)
+ response.logs
+ }
+
+}
\ No newline at end of file
diff --git a/server/src/main/scala/cn/pandadb/server/rpc/NettyRpcServer.scala b/server/src/main/scala/cn/pandadb/server/rpc/NettyRpcServer.scala
new file mode 100644
index 00000000..4b38cfee
--- /dev/null
+++ b/server/src/main/scala/cn/pandadb/server/rpc/NettyRpcServer.scala
@@ -0,0 +1,53 @@
+package cn.pandadb.server.rpc
+
+import cn.pandadb.network.internal.message.{InternalRpcRequest, InternalRpcResponse}
+import cn.pandadb.util.Logging
+
+import net.neoremind.kraps.RpcConf
+import net.neoremind.kraps.rpc.netty.NettyRpcEnvFactory
+import net.neoremind.kraps.rpc.{RpcCallContext, RpcEndpoint, RpcEnv, RpcEnvServerConfig}
+
+import scala.collection.mutable.ArrayBuffer
+
+/**
+ * Created by bluejoe on 2019/11/25.
+ */
+class NettyRpcServer(host: String, port: Int, serverName: String) extends Logging {
+ val config = RpcEnvServerConfig(new RpcConf(), serverName, host, port)
+ val thisRpcEnv = NettyRpcEnvFactory.create(config)
+ val handlers = ArrayBuffer[PartialFunction[InternalRpcRequest, InternalRpcResponse]]();
+
+ val endpoint: RpcEndpoint = new RpcEndpoint() {
+ override val rpcEnv: RpcEnv = thisRpcEnv;
+
+ override def receiveAndReply(context: RpcCallContext): PartialFunction[Any, Unit] = {
+ case request: InternalRpcRequest =>
+ val response = handlers.find {
+ _.isDefinedAt(request)
+ }.map(_.apply(request)).get
+ context.reply(response)
+ }
+ }
+
+ def accept(handler: PartialFunction[InternalRpcRequest, InternalRpcResponse]): Unit = {
+ handlers += handler;
+ }
+
+ def accept(handler: RequestHandler): Unit = {
+ handlers += handler.logic;
+ }
+
+ def start(onStarted: => Unit = {}) {
+ thisRpcEnv.setupEndpoint(serverName, endpoint)
+ onStarted;
+ thisRpcEnv.awaitTermination()
+ }
+
+ def shutdown(): Unit = {
+ thisRpcEnv.shutdown()
+ }
+}
+
+trait RequestHandler {
+ val logic: PartialFunction[InternalRpcRequest, InternalRpcResponse];
+}
\ No newline at end of file
diff --git a/server/src/main/scala/cn/pandadb/server/watchdog/forward.scala b/server/src/main/scala/cn/pandadb/server/watchdog/forward.scala
new file mode 100644
index 00000000..823e84f4
--- /dev/null
+++ b/server/src/main/scala/cn/pandadb/server/watchdog/forward.scala
@@ -0,0 +1,205 @@
+package cn.pandadb.server.watchdog
+
+import java.time.Duration
+import java.{lang, util}
+
+import cn.pandadb.server.MainServerContext
+import org.neo4j.bolt.runtime.BoltResult.Visitor
+import org.neo4j.bolt.runtime.{BoltResult, StatementMetadata, StatementProcessor, TransactionStateMachineSPI}
+import org.neo4j.bolt.v1.runtime.bookmarking.Bookmark
+import org.neo4j.cypher.result.QueryResult
+import org.neo4j.driver._
+import org.neo4j.driver.internal.util.Clock
+import org.neo4j.driver.internal.value._
+import org.neo4j.function.{ThrowingBiConsumer, ThrowingConsumer}
+import org.neo4j.graphdb.{Direction, GraphDatabaseService, Label, Node, Relationship, RelationshipType}
+import org.neo4j.kernel.impl.util.ValueUtils
+import org.neo4j.values.AnyValue
+import org.neo4j.values.virtual.MapValue
+
+import scala.collection.{JavaConversions, mutable}
+
+/**
+ * Created by bluejoe on 2019/11/4.
+ */
+object ClientPool {
+ lazy val session = {
+ val pandaString = s"panda://${MainServerContext.zkServerAddressStr}/db"
+ val driver = GraphDatabase.driver(pandaString, AuthTokens.basic("", ""));
+ driver.session();
+ }
+}
+
+class ForwardedStatementProcessor(source: StatementProcessor, spi: TransactionStateMachineSPI) extends StatementProcessor {
+ val session = ClientPool.session
+ val clock = Clock.SYSTEM;
+ var _currentTransaction: Transaction = null;
+ var _currentStatementResult: StatementResult = null;
+
+ override def markCurrentTransactionForTermination(): Unit = source.markCurrentTransactionForTermination()
+
+ override def commitTransaction(): Bookmark = source.commitTransaction()
+
+ override def run(statement: String, params: MapValue): StatementMetadata = source.run(statement, params)
+
+ override def run(statement: String, params: MapValue, bookmark: Bookmark,
+ txTimeout: Duration, txMetaData: util.Map[String, AnyRef]): StatementMetadata = {
+
+ // param transformation, contribute by codeBabyLin
+ val paramMap = new mutable.HashMap[String, AnyRef]()
+ val myConsumer = new ThrowingBiConsumer[String, AnyValue, Exception]() {
+ override def accept(var1: String, var2: AnyValue): Unit = {
+ val key = var1
+ val value = ValueUtils.asValue(var2).asObject()
+ paramMap.update(key, value)
+ }
+ }
+ params.foreach(myConsumer)
+
+ val mapTrans = JavaConversions.mapAsJavaMap(paramMap)
+ //extract metadata from _currentStatementResult.
+ _currentTransaction = session.beginTransaction();
+ _currentStatementResult = _currentTransaction.run(statement, mapTrans)
+ new MyStatementMetadata(_currentStatementResult)
+ }
+
+ override def streamResult(resultConsumer: ThrowingConsumer[BoltResult, Exception]): Bookmark = {
+ resultConsumer.accept(new MyBoltResult(_currentStatementResult));
+ //return bookmark
+ new Bookmark(spi.newestEncounteredTxId());
+ }
+
+ class MyBoltResult(result: StatementResult) extends BoltResult {
+ override def fieldNames(): Array[String] = JavaConversions.collectionAsScalaIterable(result.keys()).toArray
+
+ override def accept(visitor: Visitor): Unit = {
+ val start = clock.millis();
+
+ val it = result.stream().iterator();
+ while (it.hasNext) {
+ val record = it.next();
+ visitor.visit(new MyRecord(record));
+ }
+
+ visitor.addMetadata("result_consumed_after", org.neo4j.values.storable.Values.longValue(clock.millis() - start));
+ //query_type?
+ }
+
+ override def close(): Unit = _currentTransaction.close()
+ }
+
+ class MyRecord(record: Record) extends QueryResult.Record {
+ override def fields(): Array[AnyValue] = {
+ JavaConversions.collectionAsScalaIterable(record.values()).map {
+ value: Value =>
+ value match {
+ //TODO: check different types of XxxValue, unpack and use ValueUtils to transform
+ case v: NodeValue => ValueUtils.asAnyValue(new MyDriverNodeToDbNode(v))
+ case v: org.neo4j.driver.internal.value.MapValue => ValueUtils.asAnyValue(v.asMap())
+ case v: ListValue => ValueUtils.asAnyValue(v.asList())
+ case v: IntegerValue => ValueUtils.asAnyValue(v.asLong())
+ case v: FloatValue => ValueUtils.asAnyValue(v.asDouble())
+ case v: BooleanValue => ValueUtils.asAnyValue(v.asBoolean())
+ case v: DateValue => ValueUtils.asAnyValue(v.asLocalDate())
+ case v: DateTimeValue => ValueUtils.asAnyValue(v.asLocalDateTime())
+ case v: StringValue => ValueUtils.asAnyValue(v.asString())
+ case _ =>
+ ValueUtils.asAnyValue(value.asObject())
+ }
+ }.toArray
+ }
+ }
+
+ class MyStatementMetadata(result: StatementResult) extends StatementMetadata {
+ override def fieldNames(): Array[String] = JavaConversions.collectionAsScalaIterable(result.keys()).toArray
+ }
+
+ override def hasOpenStatement: Boolean = source.hasOpenStatement
+
+ override def rollbackTransaction(): Unit = source.rollbackTransaction()
+
+ override def hasTransaction: Boolean = source.hasTransaction
+
+ override def reset(): Unit = source.reset()
+
+ override def validateTransaction(): Unit = source.validateTransaction()
+
+ override def beginTransaction(bookmark: Bookmark): Unit = source.beginTransaction(bookmark)
+
+ override def beginTransaction(bookmark: Bookmark, txTimeout: Duration,
+ txMetadata: util.Map[String, AnyRef]): Unit =
+ source.beginTransaction(bookmark, txTimeout, txMetadata)
+
+ // class for driver node type transform to DB's node type
+ class MyDriverNodeToDbNode(driverNode: NodeValue) extends Node {
+
+ override def getId: Long = driverNode.asEntity().id()
+
+ override def delete(): Unit = {}
+
+ override def getRelationships: lang.Iterable[Relationship] = JavaConversions.asJavaIterable(None)
+
+ override def hasRelationship: Boolean = false
+
+ override def getRelationships(types: RelationshipType*): lang.Iterable[Relationship] = JavaConversions.asJavaIterable(None)
+
+ override def getRelationships(direction: Direction, types: RelationshipType*): lang.Iterable[Relationship] = JavaConversions.asJavaIterable(None)
+
+ override def hasRelationship(types: RelationshipType*): Boolean = false
+
+ override def hasRelationship(direction: Direction, types: RelationshipType*): Boolean = false
+
+ override def getRelationships(dir: Direction): lang.Iterable[Relationship] = JavaConversions.asJavaIterable(None)
+
+ override def hasRelationship(dir: Direction): Boolean = false
+
+ override def getRelationships(`type`: RelationshipType, dir: Direction): lang.Iterable[Relationship] = JavaConversions.asJavaIterable(None)
+
+ override def hasRelationship(`type`: RelationshipType, dir: Direction): Boolean = false
+
+ override def getSingleRelationship(`type`: RelationshipType, dir: Direction): Relationship = null
+
+ override def createRelationshipTo(otherNode: Node, `type`: RelationshipType): Relationship = null
+
+ override def getRelationshipTypes: lang.Iterable[RelationshipType] = JavaConversions.asJavaIterable(None)
+
+ override def getDegree: Int = 0
+
+ override def getDegree(`type`: RelationshipType): Int = 0
+
+ override def getDegree(direction: Direction): Int = 0
+
+ override def getDegree(`type`: RelationshipType, direction: Direction): Int = 0
+
+ override def addLabel(label: Label): Unit = {}
+
+ override def removeLabel(label: Label): Unit = {}
+
+ override def hasLabel(label: Label): Boolean = true
+
+ override def getLabels: lang.Iterable[Label] = {
+ val itor = JavaConversions.asScalaIterator(driverNode.asNode().labels().iterator())
+ val iter = itor.map(label => Label.label(label))
+ JavaConversions.asJavaIterable(iter.toIterable)
+ }
+
+ override def getGraphDatabase: GraphDatabaseService = null
+
+ override def hasProperty(key: String): Boolean = driverNode.get(key) != null
+
+ override def getProperty(key: String): AnyRef = driverNode.get(key).asObject()
+
+ override def getProperty(key: String, defaultValue: Any): AnyRef = driverNode.get(key, defaultValue)
+
+ override def setProperty(key: String, value: Any): Unit = {}
+
+ override def removeProperty(key: String): AnyRef = null
+
+ override def getPropertyKeys: lang.Iterable[String] = JavaConversions.asJavaIterable(None)
+
+ override def getProperties(keys: String*): util.Map[String, AnyRef] = JavaConversions.mapAsJavaMap(Map())
+
+ override def getAllProperties: util.Map[String, AnyRef] = driverNode.asEntity().asMap()
+ }
+
+}
\ No newline at end of file
diff --git a/server/src/test/resources/test_pnode0.conf b/server/src/test/resources/test_pnode0.conf
new file mode 100644
index 00000000..0ae98a18
--- /dev/null
+++ b/server/src/test/resources/test_pnode0.conf
@@ -0,0 +1,37 @@
+dbms.security.auth_enabled=false
+dbms.connector.bolt.enabled=true
+dbms.connector.bolt.tls_level=OPTIONAL
+dbms.connector.bolt.listen_address=0.0.0.0:7685
+
+dbms.connector.http.enabled=true
+dbms.connector.http.listen_address=localhost:7469
+dbms.connector.https.enabled=false
+dbms.logs.http.enabled=true
+
+blob.plugins.conf=./cypher-plugins.xml
+
+#blob.storage=cn.pidb.engine.HBaseBlobValueStorage
+blob.storage.hbase.zookeeper.port=2181
+blob.storage.hbase.zookeeper.quorum=localhost
+blob.storage.hbase.auto_create_table=true
+blob.storage.hbase.table=PIDB_BLOB
+
+blob.aipm.modules.enabled=false
+blob.aipm.modules.dir=/usr/local/aipm/modules/
+
+#blob.storage=cn.pidb.engine.FileBlobValueStorage
+#blob.storage.file.dir=/tmp
+
+#dbms.active_database=testdb
+aipm.http.host.url=http://10.0.86.128:8081/
+
+# fake address just for test, supposed to be the real ip:port of localhost:boltPort
+#serviceAddress=10.0.88.11:1111
+# zookeeper revelant args
+zkServerAddress=10.0.86.26:2181,10.0.86.27:2181,10.0.86.70:2181
+sessionTimeout=20000
+connectionTimeout=10000
+registryPath=/pandaNodes
+ordinaryNodesPath=/pandaNodes/ordinaryNodes
+leaderNodePath=/pandaNodes/leaderNode
+localNodeAddress=10.0.88.11:1111
\ No newline at end of file
diff --git a/server/src/test/scala/ClusterLogTest.scala b/server/src/test/scala/ClusterLogTest.scala
new file mode 100644
index 00000000..8fbed081
--- /dev/null
+++ b/server/src/test/scala/ClusterLogTest.scala
@@ -0,0 +1,61 @@
+import java.io.{BufferedReader, File, FileInputStream, InputStreamReader}
+
+import cn.pandadb.server.{DataLogDetail, JsonDataLogRW}
+import org.junit.{Assert, BeforeClass, Test}
+import ClusterLogTest.logFile
+/**
+ * @Author: Airzihao
+ * @Description:
+ * @Date: Created at 19:21 2019/12/1
+ * @Modified By:
+ */
+object ClusterLogTest {
+
+ val testdataPath: String = "./src/test/testdata/";
+ val logFilePath: String = "./src/test/testdata/datalog.json"
+ val logFile = new File(logFilePath)
+ @BeforeClass
+ def prepareLogFile(): Unit = {
+ if (logFile.exists()) {
+ logFile.delete()
+ logFile.createNewFile()
+ } else {
+ new File(testdataPath).mkdirs()
+ logFile.createNewFile()
+ }
+ }
+}
+
+class ClusterLogTest {
+
+ val expectedLogArray1: Array[String] = Array("Match(n2), return n2;")
+ val expectedLogArray2: Array[String] = Array("Match(n2), return n2;", "Match(n3), return n3;")
+
+ @Test
+ def test1(): Unit = {
+ val jsonDataLogRW = JsonDataLogRW.open(logFile)
+ Assert.assertEquals(0, logFile.length())
+ jsonDataLogRW.write(DataLogDetail(1, "Match(n1), return n1;"))
+ jsonDataLogRW.write(DataLogDetail(2, "Match(n2), return n2;"))
+ val _bf = new BufferedReader(new InputStreamReader(new FileInputStream(logFile)))
+ Assert.assertEquals(s"""{"versionNum":${1},"command":"Match(n1), return n1;"}""", _bf.readLine())
+ Assert.assertEquals(s"""{"versionNum":${2},"command":"Match(n2), return n2;"}""", _bf.readLine())
+ }
+
+ @Test
+ def test2(): Unit = {
+ val jsonDataLogRW = JsonDataLogRW.open(logFile)
+ val commandList = jsonDataLogRW.consume(logItem => logItem.command, 1)
+ Assert.assertEquals(expectedLogArray1.head, commandList.toList.head)
+ }
+
+ @Test
+ def test3(): Unit = {
+ val jsonDataLog = JsonDataLogRW.open(logFile)
+ jsonDataLog.write(DataLogDetail(3, "Match(n3), return n3;"))
+ val commandList = jsonDataLog.consume(logItem => logItem.command, 1)
+ val commandArr = commandList.toArray
+ Assert.assertEquals(expectedLogArray2(0), commandArr(0))
+ Assert.assertEquals(expectedLogArray2(1), commandArr(1))
+ }
+}
\ No newline at end of file
diff --git a/server/src/test/scala/NaiveLockTest.scala b/server/src/test/scala/NaiveLockTest.scala
new file mode 100644
index 00000000..8ddb22f9
--- /dev/null
+++ b/server/src/test/scala/NaiveLockTest.scala
@@ -0,0 +1,79 @@
+import NaiveLockTest._
+import cn.pandadb.network.{NodeAddress, ZookeeperBasedClusterClient}
+import cn.pandadb.server.{MasterRole, ZKServiceRegistry}
+import org.junit.runners.MethodSorters
+import org.junit.{Assert, FixMethodOrder, Test}
+/**
+ * @Author: Airzihao
+ * @Description:
+ * @Date: Created at 9:06 2019/11/28
+ * @Modified By:
+ */
+
+object NaiveLockTest {
+ val zkString = "10.0.86.26:2181"
+ val localNodeAddress = "10.0.88.11:1111"
+
+// val localPNodeServer = new LocalServerThread(0)
+ val nodeList = List("10.0.88.11:1111", "10.0.88.22:2222", "10.0.88.33:3333", "10.0.88.44:4444")
+ val clusterClient = new ZookeeperBasedClusterClient(zkString)
+ val master = {
+ val _register = new ZKServiceRegistry(zkString)
+ _register.registerAsLeader(NodeAddress.fromString("10.0.88.11:1111"))
+ val mR = new MasterRole(clusterClient, NodeAddress.fromString(localNodeAddress))
+ _register.unRegisterLeaderNode(NodeAddress.fromString("10.0.88.11:1111"))
+ _register.unRegisterOrdinaryNode(NodeAddress.fromString("10.0.88.11:1111"))
+ mR
+ }
+ val register = new ZKServiceRegistry(zkString)
+}
+
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class NaiveLockTest {
+
+ //register nodes
+ @Test
+ def test1(): Unit = {
+
+ Assert.assertEquals(true, clusterClient.getAllNodes().isEmpty)
+ Assert.assertEquals(true, clusterClient.getWriteMasterNode("").isEmpty)
+
+ nodeList.foreach(nodeStr => register.registerAsOrdinaryNode(NodeAddress.fromString(nodeStr)))
+ register.registerAsLeader(NodeAddress.fromString(nodeList.head))
+
+ Assert.assertEquals(nodeList.head, clusterClient.getWriteMasterNode("").get.getAsString)
+ val a = clusterClient.getAllNodes().map(_.getAsString).toList
+ Assert.assertEquals(true, compareList(nodeList, clusterClient.getAllNodes()))
+
+ }
+
+ // test write lock
+ @Test
+ def test2(): Unit = {
+
+ master.globalWriteLock.lock()
+ Assert.assertEquals(true, clusterClient.getAllNodes().isEmpty)
+ Assert.assertEquals(true, clusterClient.getWriteMasterNode("").isEmpty)
+ master.globalWriteLock.unlock()
+ Assert.assertEquals(nodeList.head, clusterClient.getWriteMasterNode("").get.getAsString)
+ Assert.assertEquals(true, compareList(nodeList, clusterClient.getAllNodes()))
+ }
+
+ // test read lock
+ @Test
+ def test3(): Unit = {
+ master.globalReadLock.lock()
+ Thread.sleep(3000)
+ Assert.assertEquals(true, compareList(nodeList, clusterClient.getAllNodes()))
+ Assert.assertEquals(false, clusterClient.getWriteMasterNode("").getOrElse(false))
+ master.globalReadLock.unlock()
+ Thread.sleep(3000)
+ Assert.assertEquals(nodeList.head, clusterClient.getWriteMasterNode("").get.getAsString)
+ Assert.assertEquals(true, compareList(nodeList, clusterClient.getAllNodes()))
+ }
+
+ def compareList(srtList: List[String], allNodes: Iterable[NodeAddress]): Boolean = {
+ allNodes.map(_.getAsString).toSet.equals(srtList.toSet)
+ }
+
+}
diff --git a/server/src/test/scala/RpcServerTest.scala b/server/src/test/scala/RpcServerTest.scala
new file mode 100644
index 00000000..fe340751
--- /dev/null
+++ b/server/src/test/scala/RpcServerTest.scala
@@ -0,0 +1,62 @@
+import cn.pandadb.network.internal.message.{InternalRpcRequest, InternalRpcResponse}
+import cn.pandadb.server.rpc.{NettyRpcServer, RequestHandler}
+import net.neoremind.kraps.RpcConf
+import net.neoremind.kraps.rpc.netty.NettyRpcEnvFactory
+import net.neoremind.kraps.rpc.{RpcAddress, RpcEnv, RpcEnvClientConfig}
+import org.junit.Test
+
+import scala.concurrent.Await
+import scala.concurrent.duration.Duration
+
+/**
+ * Created by bluejoe on 2020/1/7.
+ */
+class RpcServerTest {
+ @Test
+ def test1(): Unit = {
+ val client = new ExampleClient("localhost", 1234);
+ //scalastyle:off println
+ println(s"square(101)=${client.square(101)}")
+ }
+}
+
+object StartExampleRpcServer {
+ def main(args: Array[String]) {
+ val serverKernel = new NettyRpcServer("0.0.0.0", 1234, "ExampleRPC");
+ serverKernel.accept(ExampleRequestHandler());
+ serverKernel.start({
+ //scalastyle:off println
+ println(s"rpc server started!");
+ })
+ }
+}
+
+class ExampleClient(host: String, port: Int) {
+ val rpcConf = new RpcConf()
+ val config = RpcEnvClientConfig(rpcConf, "ExampleClient")
+ val rpcEnv: RpcEnv = NettyRpcEnvFactory.create(config)
+
+ val endPointRef = rpcEnv.setupEndpointRef(RpcAddress(host, port), "ExampleClient")
+
+ def close(): Unit = {
+ rpcEnv.stop(endPointRef)
+ }
+
+ def square(x: Double): Double =
+ Await.result(endPointRef.ask[CalcSquareResponse](CalcSquareRequest(x)), Duration.Inf).y;
+}
+
+case class ExampleRequestHandler() extends RequestHandler {
+ override val logic: PartialFunction[InternalRpcRequest, InternalRpcResponse] = {
+ case CalcSquareRequest(x: Double) =>
+ CalcSquareResponse(x * x)
+ }
+}
+
+case class CalcSquareRequest(x: Double) extends InternalRpcRequest {
+
+}
+
+case class CalcSquareResponse(y: Double) extends InternalRpcResponse {
+
+}
\ No newline at end of file
diff --git a/server/src/test/scala/ZKResistryTest.scala b/server/src/test/scala/ZKResistryTest.scala
new file mode 100644
index 00000000..14758ef9
--- /dev/null
+++ b/server/src/test/scala/ZKResistryTest.scala
@@ -0,0 +1,78 @@
+import cn.pandadb.network.{NodeAddress, ZKPathConfig}
+import cn.pandadb.server.ZKServiceRegistry
+import org.apache.curator.framework.{CuratorFramework, CuratorFrameworkFactory}
+import org.apache.curator.retry.ExponentialBackoffRetry
+import org.junit.runners.MethodSorters
+import org.junit.{Assert, FixMethodOrder, Test}
+
+/**
+ * @Author: Airzihao
+ * @Description:
+ * @Date: Created in 14:25 2019/11/26
+ * @Modified By:
+ */
+
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class ZKResistryTest {
+
+ val localNodeAddress = "10.0.88.11:1111"
+ val zkServerAddress = "10.0.86.26:2181"
+
+ val ordinaryNodePath = ZKPathConfig.ordinaryNodesPath + s"/" + localNodeAddress
+ val leaderNodePath = ZKPathConfig.leaderNodePath + s"/" + localNodeAddress
+
+ val curator: CuratorFramework = CuratorFrameworkFactory.newClient(zkServerAddress,
+ new ExponentialBackoffRetry(1000, 3));
+ curator.start()
+ val ordinadyNodeRegistry = new ZKServiceRegistry(zkServerAddress)
+ val leaderNodeRegistry = new ZKServiceRegistry(zkServerAddress)
+
+ // no ordinaryNode before registry.
+ @Test
+ def test1(): Unit = {
+ val flag = curator.checkExists().forPath(ordinaryNodePath)
+ Assert.assertEquals(true, flag == null)
+ }
+
+ // exist ordinaryNode after registry
+ @Test
+ def test2(): Unit = {
+ ordinadyNodeRegistry.registerAsOrdinaryNode(NodeAddress.fromString(localNodeAddress))
+ val flag = curator.checkExists().forPath(ordinaryNodePath)
+ Assert.assertEquals(true, flag != null)
+ val ordinaryNodeAddress = curator.getChildren().forPath(ZKPathConfig.ordinaryNodesPath) // returned type is ArrayList[String]
+ Assert.assertEquals("10.0.88.11:1111", ordinaryNodeAddress.get(0))
+ ordinadyNodeRegistry.curator.close()
+ }
+
+ // ordinaryNode is deleted after curator_session
+ @Test
+ def test3(): Unit = {
+ val flag = curator.checkExists().forPath(ordinaryNodePath)
+ Assert.assertEquals(true, flag == null)
+ }
+
+ // no leader node before regisried as leader node
+ @Test
+ def test4(): Unit = {
+ val flag = curator.checkExists().forPath(leaderNodePath)
+ Assert.assertEquals(true, flag == null)
+ }
+
+ // exist leader node after registry
+ @Test
+ def test5(): Unit = {
+ leaderNodeRegistry.registerAsLeader(NodeAddress.fromString(localNodeAddress))
+ val flag = curator.checkExists().forPath(leaderNodePath)
+ Assert.assertEquals(true, flag != false)
+ leaderNodeRegistry.curator.close()
+ }
+
+ // leader node is deleted after curator session closed.
+ @Test
+ def test6(): Unit = {
+ val flag = curator.checkExists().forPath(leaderNodePath)
+ Assert.assertEquals(true, flag == null)
+ }
+
+}
diff --git a/src/blob/java/org/neo4j/bolt/runtime/BoltStateMachineFactoryImpl.java b/src/blob/java/org/neo4j/bolt/runtime/BoltStateMachineFactoryImpl.java
deleted file mode 100644
index 16c9f31e..00000000
--- a/src/blob/java/org/neo4j/bolt/runtime/BoltStateMachineFactoryImpl.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (c) 2002-2019 "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.bolt.runtime;
-
-import java.time.Clock;
-import java.time.Duration;
-
-import org.neo4j.bolt.BoltChannel;
-import org.neo4j.bolt.security.auth.Authentication;
-import org.neo4j.bolt.v1.BoltProtocolV1;
-import org.neo4j.bolt.v1.runtime.BoltStateMachineV1;
-import org.neo4j.bolt.v1.runtime.BoltStateMachineV1SPI;
-import org.neo4j.bolt.v1.runtime.TransactionStateMachineV1SPI;
-import org.neo4j.bolt.v2.BoltProtocolV2;
-import org.neo4j.bolt.v3.BoltProtocolV3;
-import org.neo4j.bolt.v3.BoltStateMachineV3;
-import org.neo4j.bolt.v3.runtime.TransactionStateMachineV3SPI;
-import org.neo4j.bolt.v5.BoltProtocolV5;
-import org.neo4j.bolt.v5.BoltStateMachineV5;
-import org.neo4j.bolt.v5.runtime.TransactionStateMachineV5SPI;
-import org.neo4j.dbms.database.DatabaseManager;
-import org.neo4j.graphdb.factory.GraphDatabaseSettings;
-import org.neo4j.kernel.configuration.Config;
-import org.neo4j.kernel.impl.factory.GraphDatabaseFacade;
-import org.neo4j.logging.internal.LogService;
-import org.neo4j.udc.UsageData;
-
-public class BoltStateMachineFactoryImpl implements BoltStateMachineFactory
-{
- private final DatabaseManager databaseManager;
- private final UsageData usageData;
- private final LogService logging;
- private final Authentication authentication;
- private final Config config;
- private final Clock clock;
- private final String activeDatabaseName;
-
- public BoltStateMachineFactoryImpl( DatabaseManager databaseManager, UsageData usageData,
- Authentication authentication, Clock clock, Config config, LogService logging )
- {
- this.databaseManager = databaseManager;
- this.usageData = usageData;
- this.logging = logging;
- this.authentication = authentication;
- this.config = config;
- this.clock = clock;
- this.activeDatabaseName = config.get( GraphDatabaseSettings.active_database );
- }
-
- @Override
- public BoltStateMachine newStateMachine( long protocolVersion, BoltChannel boltChannel )
- {
- if ( protocolVersion == BoltProtocolV1.VERSION || protocolVersion == BoltProtocolV2.VERSION )
- {
- return newStateMachineV1( boltChannel );
- }
- else if ( protocolVersion == BoltProtocolV3.VERSION )
- {
- return newStateMachineV3( boltChannel );
- }
- else if ( protocolVersion == BoltProtocolV5.VERSION )
- {
- return newStateMachineV5( boltChannel );
- }
- else
- {
- throw new IllegalArgumentException( "Failed to create a state machine for protocol version " + protocolVersion );
- }
- }
-
- private BoltStateMachine newStateMachineV1( BoltChannel boltChannel )
- {
- TransactionStateMachineSPI transactionSPI = new TransactionStateMachineV1SPI( getActiveDatabase(), boltChannel, getAwaitDuration(), clock );
- BoltStateMachineSPI boltSPI = new BoltStateMachineV1SPI( usageData, logging, authentication, transactionSPI );
- return new BoltStateMachineV1( boltSPI, boltChannel, clock );
- }
-
- private BoltStateMachine newStateMachineV3( BoltChannel boltChannel )
- {
- TransactionStateMachineSPI transactionSPI = new TransactionStateMachineV3SPI( getActiveDatabase(), boltChannel, getAwaitDuration(), clock );
- BoltStateMachineSPI boltSPI = new BoltStateMachineV1SPI( usageData, logging, authentication, transactionSPI );
- return new BoltStateMachineV3( boltSPI, boltChannel, clock );
- }
-
- private BoltStateMachine newStateMachineV5( BoltChannel boltChannel )
- {
- TransactionStateMachineSPI transactionSPI = new TransactionStateMachineV5SPI( getActiveDatabase(), boltChannel, getAwaitDuration(), clock );
- BoltStateMachineSPI boltSPI = new BoltStateMachineV1SPI( usageData, logging, authentication, transactionSPI );
- return new BoltStateMachineV5( boltSPI, boltChannel, clock );
- }
-
- private Duration getAwaitDuration()
- {
- long bookmarkReadyTimeout = config.get( GraphDatabaseSettings.bookmark_ready_timeout ).toMillis();
-
- return Duration.ofMillis( bookmarkReadyTimeout );
- }
-
- private GraphDatabaseFacade getActiveDatabase()
- {
- return databaseManager.getDatabaseFacade( activeDatabaseName ).get();
- }
-}
diff --git a/src/blob/java/org/neo4j/bolt/transport/DefaultBoltProtocolFactory.java b/src/blob/java/org/neo4j/bolt/transport/DefaultBoltProtocolFactory.java
deleted file mode 100644
index 81f9d8c8..00000000
--- a/src/blob/java/org/neo4j/bolt/transport/DefaultBoltProtocolFactory.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (c) 2002-2019 "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.bolt.transport;
-
-import org.neo4j.bolt.BoltChannel;
-import org.neo4j.bolt.BoltProtocol;
-import org.neo4j.bolt.runtime.BoltConnectionFactory;
-import org.neo4j.bolt.runtime.BoltStateMachineFactory;
-import org.neo4j.bolt.v1.BoltProtocolV1;
-import org.neo4j.bolt.v2.BoltProtocolV2;
-import org.neo4j.bolt.v3.BoltProtocolV3;
-import org.neo4j.bolt.v5.BoltProtocolV5;
-import org.neo4j.logging.internal.LogService;
-
-public class DefaultBoltProtocolFactory implements BoltProtocolFactory
-{
- private final BoltConnectionFactory connectionFactory;
- private final LogService logService;
- private final BoltStateMachineFactory stateMachineFactory;
-
- public DefaultBoltProtocolFactory( BoltConnectionFactory connectionFactory, BoltStateMachineFactory stateMachineFactory,
- LogService logService )
- {
- this.connectionFactory = connectionFactory;
- this.stateMachineFactory = stateMachineFactory;
- this.logService = logService;
- }
-
- @Override
- public BoltProtocol create( long protocolVersion, BoltChannel channel )
- {
- if ( protocolVersion == BoltProtocolV1.VERSION )
- {
- return new BoltProtocolV1( channel, connectionFactory, stateMachineFactory, logService );
- }
- else if ( protocolVersion == BoltProtocolV2.VERSION )
- {
- return new BoltProtocolV2( channel, connectionFactory, stateMachineFactory, logService );
- }
- else if ( protocolVersion == BoltProtocolV3.VERSION )
- {
- return new BoltProtocolV3( channel, connectionFactory, stateMachineFactory, logService );
- }
- else if ( protocolVersion == BoltProtocolV5.VERSION )
- {
- return new BoltProtocolV5( channel, connectionFactory, stateMachineFactory, logService );
- }
- else
- {
- return null;
- }
- }
-}
diff --git a/src/blob/java/org/neo4j/bolt/v5/BoltProtocolV5.java b/src/blob/java/org/neo4j/bolt/v5/BoltProtocolV5.java
deleted file mode 100644
index b6a5a9c4..00000000
--- a/src/blob/java/org/neo4j/bolt/v5/BoltProtocolV5.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (c) 2002-2019 "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.bolt.v5;
-
-import org.neo4j.bolt.BoltChannel;
-import org.neo4j.bolt.messaging.BoltRequestMessageReader;
-import org.neo4j.bolt.messaging.Neo4jPack;
-import org.neo4j.bolt.runtime.BoltConnection;
-import org.neo4j.bolt.runtime.BoltConnectionFactory;
-import org.neo4j.bolt.runtime.BoltStateMachineFactory;
-import org.neo4j.bolt.v1.messaging.BoltResponseMessageWriterV1;
-import org.neo4j.bolt.v3.BoltProtocolV3;
-import org.neo4j.bolt.v5.request.BoltRequestMessageReaderV5;
-import org.neo4j.bolt.v5.request.messaging.Neo4jPackV5;
-import org.neo4j.logging.internal.LogService;
-
-/**
- * Bolt protocol V3. It hosts all the components that are specific to BoltV3
- */
-public class BoltProtocolV5 extends BoltProtocolV3
-{
- public static final long VERSION = 5;
-
- public BoltProtocolV5( BoltChannel channel, BoltConnectionFactory connectionFactory, BoltStateMachineFactory stateMachineFactory, LogService logging )
- {
- super( channel, connectionFactory, stateMachineFactory, logging );
- }
-
- @Override
- protected Neo4jPack createPack()
- {
- return new Neo4jPackV5();
- }
-
- @Override
- public long version()
- {
- return VERSION;
- }
-
- @Override
- protected BoltRequestMessageReader createMessageReader( BoltChannel channel, Neo4jPack neo4jPack, BoltConnection connection, LogService logging )
- {
- BoltResponseMessageWriterV1 responseWriter = new BoltResponseMessageWriterV1( neo4jPack, connection.output(), logging );
- return new BoltRequestMessageReaderV5( connection, responseWriter, logging );
- }
-}
diff --git a/src/blob/java/org/neo4j/bolt/v5/BoltStateMachineV5.java b/src/blob/java/org/neo4j/bolt/v5/BoltStateMachineV5.java
deleted file mode 100644
index 7dfb7cc7..00000000
--- a/src/blob/java/org/neo4j/bolt/v5/BoltStateMachineV5.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (c) 2002-2019 "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.bolt.v5;
-
-import org.neo4j.bolt.BoltChannel;
-import org.neo4j.bolt.runtime.BoltStateMachineSPI;
-import org.neo4j.bolt.v3.BoltStateMachineV3;
-import org.neo4j.bolt.v3.runtime.ConnectedState;
-import org.neo4j.bolt.v3.runtime.FailedState;
-import org.neo4j.bolt.v3.runtime.InterruptedState;
-import org.neo4j.bolt.v3.runtime.ReadyState;
-import org.neo4j.bolt.v3.runtime.StreamingState;
-import org.neo4j.bolt.v3.runtime.TransactionReadyState;
-import org.neo4j.bolt.v3.runtime.TransactionStreamingState;
-import org.neo4j.bolt.v5.runtime.TransactionReadyStateV5;
-
-import java.time.Clock;
-
-public class BoltStateMachineV5 extends BoltStateMachineV3
-{
- public BoltStateMachineV5( BoltStateMachineSPI boltSPI, BoltChannel boltChannel, Clock clock )
- {
- super( boltSPI, boltChannel, clock );
- }
-
- @Override
- protected States buildStates()
- {
- ConnectedState connected = new ConnectedState();
- ReadyState ready = new ReadyState();
- StreamingState streaming = new StreamingState();
-
- //supoorts blob: TransactionReadyStateV5
- TransactionReadyState txReady = new TransactionReadyStateV5();
- TransactionStreamingState txStreaming = new TransactionStreamingState();
- FailedState failed = new FailedState();
- InterruptedState interrupted = new InterruptedState();
-
- connected.setReadyState( ready );
-
- ready.setTransactionReadyState( txReady );
- ready.setStreamingState( streaming );
- ready.setFailedState( failed );
- ready.setInterruptedState( interrupted );
-
- streaming.setReadyState( ready );
- streaming.setFailedState( failed );
- streaming.setInterruptedState( interrupted );
-
- txReady.setReadyState( ready );
- txReady.setTransactionStreamingState( txStreaming );
- txReady.setFailedState( failed );
- txReady.setInterruptedState( interrupted );
-
- txStreaming.setReadyState( txReady );
- txStreaming.setFailedState( failed );
- txStreaming.setInterruptedState( interrupted );
-
- failed.setInterruptedState( interrupted );
-
- interrupted.setReadyState( ready );
-
- return new States( connected, failed );
- }
-
-}
diff --git a/src/blob/java/org/neo4j/bolt/v5/request/messaging/Neo4jPackV5.java b/src/blob/java/org/neo4j/bolt/v5/request/messaging/Neo4jPackV5.java
deleted file mode 100644
index 99e4a4b9..00000000
--- a/src/blob/java/org/neo4j/bolt/v5/request/messaging/Neo4jPackV5.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (c) 2002-2019 "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.bolt.v5.request.messaging;
-
-import cn.graiph.blob.Blob;
-import org.neo4j.bolt.blob.BoltServerBlobIO;
-import org.neo4j.bolt.v1.packstream.PackInput;
-import org.neo4j.bolt.v1.packstream.PackOutput;
-import org.neo4j.bolt.v2.messaging.Neo4jPackV2;
-import org.neo4j.values.AnyValue;
-
-import java.io.IOException;
-
-public class Neo4jPackV5 extends Neo4jPackV2
-{
- public static final long VERSION = 5;
-
- @Override
- public Packer newPacker( PackOutput output )
- {
- return new PackerV5( output );
- }
-
- @Override
- public Unpacker newUnpacker( PackInput input )
- {
- return new UnpackerV5( input );
- }
-
- @Override
- public long version()
- {
- return VERSION;
- }
-
- private static class PackerV5 extends Neo4jPackV2.PackerV2
- {
- PackerV5( PackOutput output )
- {
- super( output );
- }
-
- @Override
- public void writeBlob( Blob blob ) throws IOException
- {
- BoltServerBlobIO.packBlob( blob, out );
- }
- }
-
- private static class UnpackerV5 extends Neo4jPackV2.UnpackerV2
- {
- UnpackerV5( PackInput input )
- {
- super( input );
- }
-
- @Override
- public AnyValue unpack() throws IOException
- {
- AnyValue blobValue = BoltServerBlobIO.unpackBlob( this );
- if ( blobValue != null )
- {
- return blobValue;
- }
- return super.unpack();
- }
- }
-}
diff --git a/src/blob/java/org/neo4j/bolt/v5/runtime/TransactionReadyStateV5.java b/src/blob/java/org/neo4j/bolt/v5/runtime/TransactionReadyStateV5.java
deleted file mode 100644
index 13d8907e..00000000
--- a/src/blob/java/org/neo4j/bolt/v5/runtime/TransactionReadyStateV5.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (c) 2002-2019 "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.bolt.v5.runtime;
-
-import org.neo4j.bolt.blob.GetBlobMessage;
-import org.neo4j.bolt.messaging.RequestMessage;
-import org.neo4j.bolt.runtime.BoltStateMachineState;
-import org.neo4j.bolt.runtime.StateMachineContext;
-import org.neo4j.bolt.v3.runtime.TransactionReadyState;
-
-public class TransactionReadyStateV5 extends TransactionReadyState
-{
- @Override
- public BoltStateMachineState processUnsafe( RequestMessage message, StateMachineContext context ) throws Exception
- {
- //NOTE: get blob?
- if ( message instanceof GetBlobMessage )
- {
- ((GetBlobMessage) message).accepts( context );
- return this;
- }
-
- return super.processUnsafe( message, context );
- }
-}
diff --git a/src/blob/java/org/neo4j/consistency/checking/LabelChainWalker.java b/src/blob/java/org/neo4j/consistency/checking/LabelChainWalker.java
deleted file mode 100644
index 0c57ad63..00000000
--- a/src/blob/java/org/neo4j/consistency/checking/LabelChainWalker.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright (c) 2002-2019 "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.consistency.checking;
-
-import org.eclipse.collections.api.map.primitive.MutableLongObjectMap;
-import org.eclipse.collections.impl.map.mutable.primitive.LongObjectHashMap;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import cn.graiph.util.ContextMap;
-import org.neo4j.consistency.report.ConsistencyReport;
-import org.neo4j.consistency.store.RecordAccess;
-import org.neo4j.kernel.impl.InstanceContext;
-import org.neo4j.kernel.impl.store.LabelIdArray;
-import org.neo4j.kernel.impl.store.PropertyType;
-import org.neo4j.kernel.impl.store.record.AbstractBaseRecord;
-import org.neo4j.kernel.impl.store.record.DynamicRecord;
-import org.neo4j.kernel.impl.store.record.Record;
-
-import static org.neo4j.kernel.impl.store.AbstractDynamicStore.readFullByteArrayFromHeavyRecords;
-import static org.neo4j.kernel.impl.store.DynamicArrayStore.getRightArray;
-
-public class LabelChainWalker implements
- ComparativeRecordChecker
-{
- private final Validator validator;
-
- private final MutableLongObjectMap recordIds = new LongObjectHashMap<>();
- private final List recordList = new ArrayList<>();
- private boolean allInUse = true;
-
- public LabelChainWalker( Validator validator )
- {
- this.validator = validator;
- }
-
- @Override
- public void checkReference( RECORD record, DynamicRecord dynamicRecord,
- CheckerEngine engine,
- RecordAccess records )
- {
- recordIds.put( dynamicRecord.getId(), dynamicRecord );
-
- if ( dynamicRecord.inUse() )
- {
- recordList.add( dynamicRecord );
- }
- else
- {
- allInUse = false;
- validator.onRecordNotInUse( dynamicRecord, engine );
- }
-
- long nextBlock = dynamicRecord.getNextBlock();
- if ( Record.NO_NEXT_BLOCK.is( nextBlock ) )
- {
- if ( allInUse )
- {
- // only validate label ids if all dynamic records seen were in use
- validator.onWellFormedChain( labelIds( InstanceContext.none(), recordList ), engine, records );
- }
- }
- else
- {
- final DynamicRecord nextRecord = recordIds.get( nextBlock );
- if ( nextRecord != null )
- {
- validator.onRecordChainCycle( nextRecord, engine );
- }
- else
- {
- engine.comparativeCheck( records.nodeLabels( nextBlock ), this );
- }
- }
- }
-
- public static long[] labelIds( ContextMap ic, List recordList )
- {
- long[] idArray =
- (long[]) getRightArray( ic, readFullByteArrayFromHeavyRecords( recordList, PropertyType.ARRAY ) ).asObject();
- return LabelIdArray.stripNodeId( idArray );
- }
-
- public interface Validator
- {
- void onRecordNotInUse( DynamicRecord dynamicRecord, CheckerEngine engine );
- void onRecordChainCycle( DynamicRecord record, CheckerEngine engine );
- void onWellFormedChain( long[] labelIds, CheckerEngine engine, RecordAccess records );
- }
-}
diff --git a/src/blob/java/org/neo4j/consistency/checking/full/NodeLabelReader.java b/src/blob/java/org/neo4j/consistency/checking/full/NodeLabelReader.java
deleted file mode 100644
index af118f00..00000000
--- a/src/blob/java/org/neo4j/consistency/checking/full/NodeLabelReader.java
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright (c) 2002-2019 "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.consistency.checking.full;
-
-import org.eclipse.collections.api.set.primitive.MutableLongSet;
-import org.eclipse.collections.impl.set.mutable.primitive.LongHashSet;
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-import org.neo4j.collection.PrimitiveLongCollections;
-import org.neo4j.consistency.checking.CheckerEngine;
-import org.neo4j.consistency.checking.LabelChainWalker;
-import org.neo4j.consistency.report.ConsistencyReport;
-import org.neo4j.consistency.store.RecordAccess;
-import org.neo4j.consistency.store.RecordReference;
-import org.neo4j.kernel.impl.InstanceContext;
-import org.neo4j.kernel.impl.store.DynamicNodeLabels;
-import org.neo4j.kernel.impl.store.InlineNodeLabels;
-import org.neo4j.kernel.impl.store.NodeLabels;
-import org.neo4j.kernel.impl.store.NodeLabelsField;
-import org.neo4j.kernel.impl.store.RecordStore;
-import org.neo4j.kernel.impl.store.record.AbstractBaseRecord;
-import org.neo4j.kernel.impl.store.record.DynamicRecord;
-import org.neo4j.kernel.impl.store.record.NodeRecord;
-import org.neo4j.kernel.impl.store.record.Record;
-
-import static org.neo4j.kernel.impl.store.record.RecordLoad.FORCE;
-
-public class NodeLabelReader
-{
- private NodeLabelReader()
- {
- }
-
- public static Set getListOfLabels(
- NodeRecord nodeRecord, RecordAccess records, CheckerEngine engine )
- {
- final Set labels = new HashSet<>();
-
- NodeLabels nodeLabels = NodeLabelsField.parseLabelsField( nodeRecord );
- if ( nodeLabels instanceof DynamicNodeLabels )
- {
-
- DynamicNodeLabels dynamicNodeLabels = (DynamicNodeLabels) nodeLabels;
- long firstRecordId = dynamicNodeLabels.getFirstDynamicRecordId();
- RecordReference firstRecordReference = records.nodeLabels( firstRecordId );
- engine.comparativeCheck( firstRecordReference,
- new LabelChainWalker<>(
- new LabelChainWalker.Validator()
- {
- @Override
- public void onRecordNotInUse( DynamicRecord dynamicRecord,
- CheckerEngine engine )
- {
- }
-
- @Override
- public void onRecordChainCycle( DynamicRecord record,
- CheckerEngine engine )
- {
- }
-
- @Override
- public void onWellFormedChain( long[] labelIds,
- CheckerEngine engine,
- RecordAccess records )
- {
- copyToSet( labelIds, labels );
- }
- } ) );
- }
- else
- {
- copyToSet( nodeLabels.get( null ), labels );
- }
-
- return labels;
- }
-
- public static long[] getListOfLabels( NodeRecord nodeRecord, RecordStore labels )
- {
- long field = nodeRecord.getLabelField();
- if ( NodeLabelsField.fieldPointsToDynamicRecordOfLabels( field ) )
- {
- List recordList = new ArrayList<>();
- final MutableLongSet alreadySeen = new LongHashSet();
- long id = NodeLabelsField.firstDynamicLabelRecordId( field );
- while ( !Record.NULL_REFERENCE.is( id ) )
- {
- DynamicRecord record = labels.getRecord( id, labels.newRecord(), FORCE );
- if ( !record.inUse() || !alreadySeen.add( id ) )
- {
- return PrimitiveLongCollections.EMPTY_LONG_ARRAY;
- }
- recordList.add( record );
- }
- return LabelChainWalker.labelIds( InstanceContext.of(labels), recordList );
- }
- return InlineNodeLabels.get( nodeRecord );
- }
-
- public static Set getListOfLabels( long labelField )
- {
- final Set labels = new HashSet<>();
- copyToSet( InlineNodeLabels.parseInlined(labelField), labels );
-
- return labels;
- }
-
- private static void copyToSet( long[] array, Set set )
- {
- for ( long labelId : array )
- {
- set.add( labelId );
- }
- }
-}
diff --git a/src/blob/java/org/neo4j/graphdb/factory/GraphDatabaseBuilder.java b/src/blob/java/org/neo4j/graphdb/factory/GraphDatabaseBuilder.java
deleted file mode 100644
index 0519d249..00000000
--- a/src/blob/java/org/neo4j/graphdb/factory/GraphDatabaseBuilder.java
+++ /dev/null
@@ -1,269 +0,0 @@
-/*
- * Copyright (c) 2002-2019 "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.graphdb.factory;
-
-import java.io.File;
-import java.io.InputStream;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Properties;
-import java.util.Set;
-import javax.annotation.Nonnull;
-
-import org.neo4j.graphdb.GraphDatabaseService;
-import org.neo4j.graphdb.config.Setting;
-import org.neo4j.kernel.configuration.Config;
-
-import static org.neo4j.helpers.collection.MapUtil.stringMap;
-
-/**
- * Builder for {@link GraphDatabaseService}s that allows for setting and loading
- * configuration.
- */
-public class GraphDatabaseBuilder
-{
- /**
- * @deprecated This will be moved to an internal package in the future.
- */
- @Deprecated
- public interface DatabaseCreator
- {
- /**
- * @param config initial configuration for the database.
- * @return an instance of {@link GraphDatabaseService}.
- * @deprecated this method will go away in 4.0. See {@link #newDatabase(Config)} instead.
- */
- @Deprecated
- default GraphDatabaseService newDatabase( Map config )
- {
- return newDatabase( Config.defaults( config ) );
- }
-
- /**
- * @param config initial configuration for the database.
- * @return an instance of {@link GraphDatabaseService}.
- */
- default GraphDatabaseService newDatabase( @Nonnull Config config )
- {
- return newDatabase( config.getRaw() );
- }
- }
-
- protected DatabaseCreator creator;
- protected Map config = new HashMap<>();
-
- /**
- * @deprecated
- */
- @Deprecated
- public GraphDatabaseBuilder( DatabaseCreator creator )
- {
- this.creator = creator;
- }
-
- /**
- * Set a database setting to a particular value.
- *
- * @param setting Database setting to set
- * @param value New value of the setting
- * @return the builder
- */
- public GraphDatabaseBuilder setConfig( Setting> setting, String value )
- {
- if ( value == null )
- {
- config.remove( setting.name() );
- }
- else
- {
- // Test if we can get this setting with an updated config
- Map testValue = stringMap( setting.name(), value );
- setting.apply( key -> testValue.containsKey( key ) ? testValue.get( key ) : config.get( key ) );
-
- // No exception thrown, add it to existing config
- config.put( setting.name(), value );
- }
- return this;
- }
-
- /**
- * Set an unvalidated configuration option.
- *
- * @param name Name of the setting
- * @param value New value of the setting
- * @return the builder
- * @deprecated Use setConfig with explicit {@link Setting} instead.
- */
- @Deprecated
- public GraphDatabaseBuilder setConfig( String name, String value )
- {
- if ( value == null )
- {
- config.remove( name );
- }
- else
- {
- config.put( name, value );
- }
- return this;
- }
-
- /**
- * Set a map of configuration settings into the builder. Overwrites any existing values.
- *
- * @param config Map of configuration settings
- * @return the builder
- * @deprecated Use setConfig with explicit {@link Setting} instead
- */
- @Deprecated
- @SuppressWarnings( "deprecation" )
- public GraphDatabaseBuilder setConfig( Map config )
- {
- for ( Map.Entry stringStringEntry : config.entrySet() )
- {
- setConfig( stringStringEntry.getKey(), stringStringEntry.getValue() );
- }
- return this;
- }
-
- /**
- * Load a Properties file from a given file, and add the settings to
- * the builder.
- *
- * @param fileName Filename of properties file to use
- * @return the builder
- * @throws IllegalArgumentException if the builder was unable to load from the given filename
- */
- public GraphDatabaseBuilder loadPropertiesFromFile( String fileName )
- throws IllegalArgumentException
- {
- try
- {
- return loadPropertiesFromURL( new File( fileName ).toURI().toURL() ).setConfig("config.file.path", fileName);
- }
- catch ( MalformedURLException e )
- {
- throw new IllegalArgumentException( "Illegal filename:" + fileName, e );
- }
- }
-
- /**
- * Load Properties file from a given URL, and add the settings to
- * the builder.
- *
- * @param url URL of properties file to use
- * @return the builder
- */
- public GraphDatabaseBuilder loadPropertiesFromURL( URL url )
- throws IllegalArgumentException
- {
- Properties props = new Properties();
- try
- {
- try ( InputStream stream = url.openStream() )
- {
- props.load( stream );
- }
- }
- catch ( Exception e )
- {
- throw new IllegalArgumentException( "Unable to load " + url, e );
- }
- Set> entries = props.entrySet();
- for ( Map.Entry