Skip to content

Improve Record#get(String key) complexity to O(1) #696

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 14, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 19 additions & 12 deletions driver/src/main/java/org/neo4j/driver/internal/InternalRecord.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,36 +22,43 @@
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.function.Function;

import org.neo4j.driver.internal.types.InternalMapAccessorWithDefaultValue;
import org.neo4j.driver.internal.util.Extract;
import org.neo4j.driver.Record;
import org.neo4j.driver.Value;
import org.neo4j.driver.Values;
import java.util.function.Function;
import org.neo4j.driver.internal.types.InternalMapAccessorWithDefaultValue;
import org.neo4j.driver.internal.util.Extract;
import org.neo4j.driver.internal.util.QueryKeys;
import org.neo4j.driver.util.Pair;

import static java.lang.String.format;
import static org.neo4j.driver.internal.util.Format.formatPairs;
import static org.neo4j.driver.Values.ofObject;
import static org.neo4j.driver.Values.ofValue;
import static org.neo4j.driver.internal.util.Format.formatPairs;

public class InternalRecord extends InternalMapAccessorWithDefaultValue implements Record
{
private final List<String> keys;
private final QueryKeys queryKeys;
private final Value[] values;
private int hashCode = 0;

public InternalRecord( List<String> keys, Value[] values )
{
this.keys = keys;
this.queryKeys = new QueryKeys( keys );
this.values = values;
}

public InternalRecord( QueryKeys queryKeys, Value[] values )
{
this.queryKeys = queryKeys;
this.values = values;
}

@Override
public List<String> keys()
{
return keys;
return queryKeys.keys();
}

@Override
Expand All @@ -69,7 +76,7 @@ public List<Pair<String, Value>> fields()
@Override
public int index( String key )
{
int result = keys.indexOf( key );
int result = queryKeys.indexOf( key );
if ( result == -1 )
{
throw new NoSuchElementException( "Unknown key: " + key );
Expand All @@ -83,13 +90,13 @@ public int index( String key )
@Override
public boolean containsKey( String key )
{
return keys.contains( key );
return queryKeys.contains( key );
}

@Override
public Value get( String key )
{
int fieldIndex = keys.indexOf( key );
int fieldIndex = queryKeys.indexOf( key );

if ( fieldIndex == -1 )
{
Expand Down Expand Up @@ -146,7 +153,7 @@ else if ( other instanceof Record )
{
return false;
}
if ( ! keys.equals( otherRecord.keys() ) )
if ( ! queryKeys.equals( otherRecord.keys() ) )
{
return false;
}
Expand All @@ -172,7 +179,7 @@ public int hashCode()
{
if ( hashCode == 0 )
{
hashCode = 31 * keys.hashCode() + Arrays.hashCode( values );
hashCode = 31 * queryKeys.hashCode() + Arrays.hashCode( values );
}
return hashCode;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public AsyncResultCursorImpl(RunResponseHandler runHandler, PullAllResponseHandl
@Override
public List<String> keys()
{
return runHandler.queryKeys();
return runHandler.queryKeys().keys();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public RxResultCursorImpl(Throwable runError, RunResponseHandler runHandler, Pul
@Override
public List<String> keys()
{
return runHandler.queryKeys();
return runHandler.queryKeys().keys();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,21 @@
*/
package org.neo4j.driver.internal.handlers;

import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;

import org.neo4j.driver.Value;
import org.neo4j.driver.internal.spi.ResponseHandler;
import org.neo4j.driver.internal.util.MetadataExtractor;
import org.neo4j.driver.Value;

import static java.util.Collections.emptyList;
import org.neo4j.driver.internal.util.QueryKeys;

public class RunResponseHandler implements ResponseHandler
{
private final CompletableFuture<Throwable> runCompletedFuture;
private final MetadataExtractor metadataExtractor;
private long queryId = MetadataExtractor.ABSENT_QUERY_ID;

private List<String> queryKeys = emptyList();
private QueryKeys queryKeys = QueryKeys.empty();
private long resultAvailableAfter = -1;

public RunResponseHandler( MetadataExtractor metadataExtractor )
Expand Down Expand Up @@ -70,7 +68,7 @@ public void onRecord( Value[] fields )
throw new UnsupportedOperationException();
}

public List<String> queryKeys()
public QueryKeys queryKeys()
{
return queryKeys;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
*/
package org.neo4j.driver.internal.util;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
Expand All @@ -40,11 +39,10 @@
import org.neo4j.driver.summary.Notification;
import org.neo4j.driver.summary.Plan;
import org.neo4j.driver.summary.ProfiledPlan;
import org.neo4j.driver.summary.QueryType;
import org.neo4j.driver.summary.ResultSummary;
import org.neo4j.driver.summary.ServerInfo;
import org.neo4j.driver.summary.QueryType;

import static java.util.Collections.emptyList;
import static org.neo4j.driver.internal.summary.InternalDatabaseInfo.DEFAULT_DATABASE_INFO;
import static org.neo4j.driver.internal.types.InternalTypeSystem.TYPE_SYSTEM;

Expand All @@ -60,14 +58,14 @@ public MetadataExtractor( String resultAvailableAfterMetadataKey, String resultC
this.resultConsumedAfterMetadataKey = resultConsumedAfterMetadataKey;
}

public List<String> extractQueryKeys(Map<String,Value> metadata )
public QueryKeys extractQueryKeys( Map<String,Value> metadata )
{
Value keysValue = metadata.get( "fields" );
if ( keysValue != null )
{
if ( !keysValue.isEmpty() )
{
List<String> keys = new ArrayList<>( keysValue.size() );
QueryKeys keys = new QueryKeys( keysValue.size() );
for ( Value value : keysValue.values() )
{
keys.add( value.asString() );
Expand All @@ -76,7 +74,7 @@ public List<String> extractQueryKeys(Map<String,Value> metadata )
return keys;
}
}
return emptyList();
return QueryKeys.empty();
}

public long extractQueryId( Map<String,Value> metadata )
Expand Down
112 changes: 112 additions & 0 deletions driver/src/main/java/org/neo4j/driver/internal/util/QueryKeys.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/*
* Copyright (c) 2002-2020 "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.util;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import static java.util.Collections.emptyList;
import static java.util.Collections.emptyMap;

public class QueryKeys
{
private static final QueryKeys EMPTY = new QueryKeys( emptyList(), emptyMap() );

private final List<String> keys;
private final Map<String,Integer> keyIndex;

public QueryKeys( int size )
{
this( new ArrayList<>( size ), new HashMap<>( size ) );
}

public QueryKeys( List<String> keys )
{
this.keys = keys;
Map<String,Integer> keyIndex = new HashMap<>( keys.size() );
int i = 0;
for ( String key : keys )
{
keyIndex.put( key, i++ );
}
this.keyIndex = keyIndex;
}

public QueryKeys( List<String> keys, Map<String,Integer> keyIndex )
{
this.keys = keys;
this.keyIndex = keyIndex;
}

public void add( String key )
{
int index = keys.size();
keys.add( key );
keyIndex.put( key, index );
}

public List<String> keys()
{
return keys;
}

public Map<String,Integer> keyIndex()
{
return keyIndex;
}

public static QueryKeys empty()
{
return EMPTY;
}

public int indexOf( String key )
{
return keyIndex.getOrDefault( key, -1 );
}

public boolean contains( String key )
{
return keyIndex.containsKey( key );
}

@Override
public boolean equals( Object o )
{
if ( this == o )
{
return true;
}
if ( o == null || getClass() != o.getClass() )
{
return false;
}
QueryKeys queryKeys = (QueryKeys) o;
return keys.equals( queryKeys.keys ) && keyIndex.equals( queryKeys.keyIndex );
}

@Override
public int hashCode()
{
return Objects.hash( keys, keyIndex );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import org.neo4j.driver.internal.handlers.pulln.BasicPullResponseHandler;
import org.neo4j.driver.internal.spi.Connection;
import org.neo4j.driver.internal.util.MetadataExtractor;
import org.neo4j.driver.internal.util.QueryKeys;
import org.neo4j.driver.internal.value.BooleanValue;
import org.neo4j.driver.Record;
import org.neo4j.driver.Value;
Expand Down Expand Up @@ -72,7 +73,7 @@ private ListBasedPullHandler( List<Record> list, Throwable error )
if ( list.size() > 1 )
{
Record record = list.get( 0 );
when( super.runResponseHandler.queryKeys() ).thenReturn( record.keys() );
when( super.runResponseHandler.queryKeys() ).thenReturn( new QueryKeys( record.keys() ) );
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import org.junit.jupiter.api.Test;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
Expand Down Expand Up @@ -77,15 +78,23 @@ class MetadataExtractorTest
void shouldExtractQueryKeys()
{
List<String> keys = asList( "hello", " ", "world", "!" );
List<String> extractedKeys = extractor.extractQueryKeys( singletonMap( "fields", value( keys ) ) );
assertEquals( keys, extractedKeys );
Map<String, Integer> keyIndex = new HashMap<>();
keyIndex.put( "hello", 0 );
keyIndex.put( " ", 1 );
keyIndex.put( "world", 2 );
keyIndex.put( "!", 3 );

QueryKeys extracted = extractor.extractQueryKeys( singletonMap( "fields", value( keys ) ) );
assertEquals( keys, extracted.keys() );
assertEquals( keyIndex, extracted.keyIndex() );
}

@Test
void shouldExtractEmptyQueryKeysWhenNoneInMetadata()
{
List<String> extractedKeys = extractor.extractQueryKeys( emptyMap() );
assertEquals( emptyList(), extractedKeys );
QueryKeys extracted = extractor.extractQueryKeys( emptyMap() );
assertEquals( emptyList(), extracted.keys() );
assertEquals( emptyMap(), extracted.keyIndex() );
}

@Test
Expand Down