org.neo4j.driver.internal.InternalResultCursor Maven / Gradle / Ivy
/**
* Copyright (c) 2002-2016 "Neo Technology,"
* Network Engine for Objects in Lund AB [http://neotechnology.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;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import org.neo4j.driver.internal.spi.Connection;
import org.neo4j.driver.internal.spi.StreamCollector;
import org.neo4j.driver.internal.summary.SummaryBuilder;
import org.neo4j.driver.v1.Function;
import org.neo4j.driver.v1.Notification;
import org.neo4j.driver.v1.Plan;
import org.neo4j.driver.v1.ProfiledPlan;
import org.neo4j.driver.v1.Record;
import org.neo4j.driver.v1.RecordAccessor;
import org.neo4j.driver.v1.ResultCursor;
import org.neo4j.driver.v1.ResultSummary;
import org.neo4j.driver.v1.Statement;
import org.neo4j.driver.v1.StatementType;
import org.neo4j.driver.v1.Transaction;
import org.neo4j.driver.v1.UpdateStatistics;
import org.neo4j.driver.v1.Value;
import org.neo4j.driver.v1.exceptions.ClientException;
import org.neo4j.driver.v1.exceptions.Neo4jException;
import org.neo4j.driver.v1.exceptions.NoSuchRecordException;
import static java.lang.String.format;
import static java.util.Collections.emptyList;
import static org.neo4j.driver.v1.Records.recordAsIs;
public class InternalResultCursor extends InternalRecordAccessor implements ResultCursor
{
private final Connection connection;
private final Transaction transaction;
private final StreamCollector runResponseCollector;
private final StreamCollector pullAllResponseCollector;
private final Queue recordBuffer = new LinkedList<>();
private List keys = null;
private ResultSummary summary = null;
private boolean open = true;
private Record current = null;
private long position = -1;
private long limit = -1;
private boolean done = false;
public InternalResultCursor( Connection connection, Transaction tx, String statement, Map parameters )
{
this.connection = connection;
this.transaction = tx;
this.runResponseCollector = newRunResponseCollector();
this.pullAllResponseCollector = newPullAllResponseCollector( new Statement( statement, parameters ) );
}
private StreamCollector newRunResponseCollector()
{
return new StreamCollector()
{
@Override
public void keys( String[] names )
{
keys = Arrays.asList( names );
}
@Override
public void record( Value[] fields ) {}
@Override
public void statementType( StatementType type ) {}
@Override
public void statementStatistics( UpdateStatistics statistics ) {}
@Override
public void plan( Plan plan ) {}
@Override
public void profile( ProfiledPlan plan ) {}
@Override
public void notifications( List notifications ) {}
@Override
public void done()
{
if ( keys == null )
{
keys = new ArrayList<>();
}
}
};
}
private StreamCollector newPullAllResponseCollector( Statement statement )
{
final SummaryBuilder summaryBuilder = new SummaryBuilder( statement );
return new StreamCollector()
{
@Override
public void keys( String[] names ) {}
@Override
public void record( Value[] fields )
{
recordBuffer.add( new InternalRecord( keys, fields ) );
}
@Override
public void statementType( StatementType type )
{
summaryBuilder.statementType( type );
}
@Override
public void statementStatistics( UpdateStatistics statistics )
{
summaryBuilder.statementStatistics( statistics );
}
@Override
public void plan( Plan plan )
{
summaryBuilder.plan( plan );
}
@Override
public void profile( ProfiledPlan plan )
{
summaryBuilder.profile( plan );
}
@Override
public void notifications( List notifications )
{
summaryBuilder.notifications( notifications );
}
@Override
public void done() {
summary = summaryBuilder.build();
done = true;
}
};
}
StreamCollector runResponseCollector()
{
return runResponseCollector;
}
StreamCollector pullAllResponseCollector()
{
return pullAllResponseCollector;
}
private void receiveOne()
{
try
{
connection.receiveOne();
}
catch ( Neo4jException ex )
{
if (transaction != null)
{
transaction.defunct();
}
throw ex;
}
}
@Override
public boolean isOpen()
{
return open;
}
public Value get( int index )
{
return record().get( index );
}
public Value get( String key )
{
return record().get( key );
}
@Override
public boolean containsKey( String key )
{
return keys.contains( key );
}
@Override
public int index( String key )
{
return record().index( key );
}
public List keys()
{
while (keys == null && !done) {
receiveOne();
}
return keys;
}
@Override
public int size()
{
return keys.size();
}
@Override
public Record record()
{
if ( current != null )
{
return current;
}
else
{
throw new NoSuchRecordException(
"In order to access the fields of a record in a result, " +
"you must first call next() to point the result to the next record in the result stream."
);
}
}
@Override
public long position()
{
assertOpen();
return position;
}
@Override
public boolean atEnd()
{
assertOpen();
if (!recordBuffer.isEmpty())
{
return false;
}
else if (done)
{
return true;
}
else
{
while ( recordBuffer.isEmpty() && !done )
{
receiveOne();
}
return recordBuffer.isEmpty() && done;
}
}
@Override
public boolean next()
{
assertOpen();
Record nextRecord = recordBuffer.poll();
if ( nextRecord != null )
{
current = nextRecord;
position += 1;
if ( position == limit )
{
discard();
}
return true;
}
else if ( done )
{
return false;
}
else
{
while ( recordBuffer.isEmpty() && !done )
{
receiveOne();
}
return next();
}
}
@Override
public long skip( long elements )
{
if ( elements < 0 )
{
throw new ClientException( "Cannot skip negative number of elements" );
}
else
{
int skipped = 0;
while ( skipped < elements && next() )
{
skipped += 1;
}
return skipped;
}
}
@Override
public long limit( long records )
{
if ( records < 0 )
{
throw new ClientException( "Cannot limit negative number of elements" );
}
else if ( records == 0) {
this.limit = position;
discard();
} else {
this.limit = records + position;
}
return this.limit;
}
@Override
public Record first()
{
if( position() >= 1 )
{
throw new NoSuchRecordException( "Cannot retrieve the first record, because this result cursor has been moved already. " +
"Please ensure you are not calling `first` multiple times, or are mixing it with calls " +
"to `next`, `single`, `list` or any other method that changes the position of the cursor." );
}
if( position == 0 )
{
return record();
}
if( !next() )
{
throw new NoSuchRecordException( "Cannot retrieve the first record, because this result is empty." );
}
return record();
}
@Override
public Value first(String fieldName) throws NoSuchRecordException
{
return first().get( fieldName );
}
@Override
public Value first(int index) throws NoSuchRecordException
{
return first().get( index );
}
@Override
public Record single()
{
Record first = first();
if( !atEnd() )
{
throw new NoSuchRecordException( "Expected a result with a single record, but this result contains at least one more. " +
"Ensure your query returns only one record, or use `first` instead of `single` if " +
"you do not care about the number of records in the result." );
}
return first;
}
@Override
public Value single( String fieldName ) throws NoSuchRecordException
{
return single().get( fieldName );
}
@Override
public Value single( int index ) throws NoSuchRecordException
{
return single().get( index );
}
@Override
public Record peek()
{
assertOpen();
Record nextRecord = recordBuffer.peek();
if ( nextRecord != null )
{
return nextRecord;
}
else if ( done )
{
return null;
}
else
{
while ( recordBuffer.isEmpty() && !done )
{
receiveOne();
}
return peek();
}
}
@Override
public List list()
{
return list( recordAsIs() );
}
@Override
public List list( Function mapFunction )
{
if ( isEmpty() )
{
assertOpen();
return emptyList();
}
else if ( position == 0 || ( position == -1 && next() ) )
{
List result = new ArrayList<>();
do
{
result.add( mapFunction.apply( this ) );
}
while ( next() );
discard();
return result;
}
else
{
throw new ClientException(
format( "Can't retain records when cursor is not pointing at the first record (currently at position %d)", position )
);
}
}
@SuppressWarnings("StatementWithEmptyBody")
@Override
public ResultSummary summarize()
{
while ( next() ) ;
return summary;
}
@Override
public void close()
{
if ( open )
{
discard();
open = false;
}
else
{
throw new ClientException( "Already closed" );
}
}
private void assertOpen()
{
if ( !open )
{
throw new ClientException( "Cursor already closed" );
}
}
private boolean isEmpty()
{
return position == -1 && recordBuffer.isEmpty() && done;
}
private void discard()
{
assertOpen();
recordBuffer.clear();
while ( !done )
{
receiveOne();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy