Skip to content

Commit cc08efb

Browse files
authored
Merge pull request #696 from zhenlineo/4.0-record-get-with-key
Improve Record#get(String key) complexity to O(1)
2 parents a410b78 + db8a804 commit cc08efb

File tree

9 files changed

+166
-33
lines changed

9 files changed

+166
-33
lines changed

driver/src/main/java/org/neo4j/driver/internal/InternalRecord.java

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,36 +22,43 @@
2222
import java.util.List;
2323
import java.util.Map;
2424
import java.util.NoSuchElementException;
25+
import java.util.function.Function;
2526

26-
import org.neo4j.driver.internal.types.InternalMapAccessorWithDefaultValue;
27-
import org.neo4j.driver.internal.util.Extract;
2827
import org.neo4j.driver.Record;
2928
import org.neo4j.driver.Value;
3029
import org.neo4j.driver.Values;
31-
import java.util.function.Function;
30+
import org.neo4j.driver.internal.types.InternalMapAccessorWithDefaultValue;
31+
import org.neo4j.driver.internal.util.Extract;
32+
import org.neo4j.driver.internal.util.QueryKeys;
3233
import org.neo4j.driver.util.Pair;
3334

3435
import static java.lang.String.format;
35-
import static org.neo4j.driver.internal.util.Format.formatPairs;
3636
import static org.neo4j.driver.Values.ofObject;
3737
import static org.neo4j.driver.Values.ofValue;
38+
import static org.neo4j.driver.internal.util.Format.formatPairs;
3839

3940
public class InternalRecord extends InternalMapAccessorWithDefaultValue implements Record
4041
{
41-
private final List<String> keys;
42+
private final QueryKeys queryKeys;
4243
private final Value[] values;
4344
private int hashCode = 0;
4445

4546
public InternalRecord( List<String> keys, Value[] values )
4647
{
47-
this.keys = keys;
48+
this.queryKeys = new QueryKeys( keys );
49+
this.values = values;
50+
}
51+
52+
public InternalRecord( QueryKeys queryKeys, Value[] values )
53+
{
54+
this.queryKeys = queryKeys;
4855
this.values = values;
4956
}
5057

5158
@Override
5259
public List<String> keys()
5360
{
54-
return keys;
61+
return queryKeys.keys();
5562
}
5663

5764
@Override
@@ -69,7 +76,7 @@ public List<Pair<String, Value>> fields()
6976
@Override
7077
public int index( String key )
7178
{
72-
int result = keys.indexOf( key );
79+
int result = queryKeys.indexOf( key );
7380
if ( result == -1 )
7481
{
7582
throw new NoSuchElementException( "Unknown key: " + key );
@@ -83,13 +90,13 @@ public int index( String key )
8390
@Override
8491
public boolean containsKey( String key )
8592
{
86-
return keys.contains( key );
93+
return queryKeys.contains( key );
8794
}
8895

8996
@Override
9097
public Value get( String key )
9198
{
92-
int fieldIndex = keys.indexOf( key );
99+
int fieldIndex = queryKeys.indexOf( key );
93100

94101
if ( fieldIndex == -1 )
95102
{
@@ -146,7 +153,7 @@ else if ( other instanceof Record )
146153
{
147154
return false;
148155
}
149-
if ( ! keys.equals( otherRecord.keys() ) )
156+
if ( !queryKeys.keys().equals( otherRecord.keys() ) )
150157
{
151158
return false;
152159
}
@@ -172,7 +179,7 @@ public int hashCode()
172179
{
173180
if ( hashCode == 0 )
174181
{
175-
hashCode = 31 * keys.hashCode() + Arrays.hashCode( values );
182+
hashCode = 31 * queryKeys.hashCode() + Arrays.hashCode( values );
176183
}
177184
return hashCode;
178185
}

driver/src/main/java/org/neo4j/driver/internal/cursor/AsyncResultCursorImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public AsyncResultCursorImpl(RunResponseHandler runHandler, PullAllResponseHandl
4545
@Override
4646
public List<String> keys()
4747
{
48-
return runHandler.queryKeys();
48+
return runHandler.queryKeys().keys();
4949
}
5050

5151
@Override

driver/src/main/java/org/neo4j/driver/internal/cursor/RxResultCursorImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ public RxResultCursorImpl(Throwable runError, RunResponseHandler runHandler, Pul
6565
@Override
6666
public List<String> keys()
6767
{
68-
return runHandler.queryKeys();
68+
return runHandler.queryKeys().keys();
6969
}
7070

7171
@Override

driver/src/main/java/org/neo4j/driver/internal/handlers/RunResponseHandler.java

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,23 +18,21 @@
1818
*/
1919
package org.neo4j.driver.internal.handlers;
2020

21-
import java.util.List;
2221
import java.util.Map;
2322
import java.util.concurrent.CompletableFuture;
2423

24+
import org.neo4j.driver.Value;
2525
import org.neo4j.driver.internal.spi.ResponseHandler;
2626
import org.neo4j.driver.internal.util.MetadataExtractor;
27-
import org.neo4j.driver.Value;
28-
29-
import static java.util.Collections.emptyList;
27+
import org.neo4j.driver.internal.util.QueryKeys;
3028

3129
public class RunResponseHandler implements ResponseHandler
3230
{
3331
private final CompletableFuture<Throwable> runCompletedFuture;
3432
private final MetadataExtractor metadataExtractor;
3533
private long queryId = MetadataExtractor.ABSENT_QUERY_ID;
3634

37-
private List<String> queryKeys = emptyList();
35+
private QueryKeys queryKeys = QueryKeys.empty();
3836
private long resultAvailableAfter = -1;
3937

4038
public RunResponseHandler( MetadataExtractor metadataExtractor )
@@ -70,7 +68,7 @@ public void onRecord( Value[] fields )
7068
throw new UnsupportedOperationException();
7169
}
7270

73-
public List<String> queryKeys()
71+
public QueryKeys queryKeys()
7472
{
7573
return queryKeys;
7674
}

driver/src/main/java/org/neo4j/driver/internal/util/MetadataExtractor.java

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
*/
1919
package org.neo4j.driver.internal.util;
2020

21-
import java.util.ArrayList;
2221
import java.util.Collections;
2322
import java.util.List;
2423
import java.util.Map;
@@ -40,11 +39,10 @@
4039
import org.neo4j.driver.summary.Notification;
4140
import org.neo4j.driver.summary.Plan;
4241
import org.neo4j.driver.summary.ProfiledPlan;
42+
import org.neo4j.driver.summary.QueryType;
4343
import org.neo4j.driver.summary.ResultSummary;
4444
import org.neo4j.driver.summary.ServerInfo;
45-
import org.neo4j.driver.summary.QueryType;
4645

47-
import static java.util.Collections.emptyList;
4846
import static org.neo4j.driver.internal.summary.InternalDatabaseInfo.DEFAULT_DATABASE_INFO;
4947
import static org.neo4j.driver.internal.types.InternalTypeSystem.TYPE_SYSTEM;
5048

@@ -60,14 +58,14 @@ public MetadataExtractor( String resultAvailableAfterMetadataKey, String resultC
6058
this.resultConsumedAfterMetadataKey = resultConsumedAfterMetadataKey;
6159
}
6260

63-
public List<String> extractQueryKeys(Map<String,Value> metadata )
61+
public QueryKeys extractQueryKeys( Map<String,Value> metadata )
6462
{
6563
Value keysValue = metadata.get( "fields" );
6664
if ( keysValue != null )
6765
{
6866
if ( !keysValue.isEmpty() )
6967
{
70-
List<String> keys = new ArrayList<>( keysValue.size() );
68+
QueryKeys keys = new QueryKeys( keysValue.size() );
7169
for ( Value value : keysValue.values() )
7270
{
7371
keys.add( value.asString() );
@@ -76,7 +74,7 @@ public List<String> extractQueryKeys(Map<String,Value> metadata )
7674
return keys;
7775
}
7876
}
79-
return emptyList();
77+
return QueryKeys.empty();
8078
}
8179

8280
public long extractQueryId( Map<String,Value> metadata )
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/*
2+
* Copyright (c) 2002-2020 "Neo4j,"
3+
* Neo4j Sweden AB [http://neo4j.com]
4+
*
5+
* This file is part of Neo4j.
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*/
19+
package org.neo4j.driver.internal.util;
20+
21+
import java.util.ArrayList;
22+
import java.util.HashMap;
23+
import java.util.List;
24+
import java.util.Map;
25+
import java.util.Objects;
26+
27+
import static java.util.Collections.emptyList;
28+
import static java.util.Collections.emptyMap;
29+
30+
public class QueryKeys
31+
{
32+
private static final QueryKeys EMPTY = new QueryKeys( emptyList(), emptyMap() );
33+
34+
private final List<String> keys;
35+
private final Map<String,Integer> keyIndex;
36+
37+
public QueryKeys( int size )
38+
{
39+
this( new ArrayList<>( size ), new HashMap<>( size ) );
40+
}
41+
42+
public QueryKeys( List<String> keys )
43+
{
44+
this.keys = keys;
45+
Map<String,Integer> keyIndex = new HashMap<>( keys.size() );
46+
int i = 0;
47+
for ( String key : keys )
48+
{
49+
keyIndex.put( key, i++ );
50+
}
51+
this.keyIndex = keyIndex;
52+
}
53+
54+
public QueryKeys( List<String> keys, Map<String,Integer> keyIndex )
55+
{
56+
this.keys = keys;
57+
this.keyIndex = keyIndex;
58+
}
59+
60+
public void add( String key )
61+
{
62+
int index = keys.size();
63+
keys.add( key );
64+
keyIndex.put( key, index );
65+
}
66+
67+
public List<String> keys()
68+
{
69+
return keys;
70+
}
71+
72+
public Map<String,Integer> keyIndex()
73+
{
74+
return keyIndex;
75+
}
76+
77+
public static QueryKeys empty()
78+
{
79+
return EMPTY;
80+
}
81+
82+
public int indexOf( String key )
83+
{
84+
return keyIndex.getOrDefault( key, -1 );
85+
}
86+
87+
public boolean contains( String key )
88+
{
89+
return keyIndex.containsKey( key );
90+
}
91+
92+
@Override
93+
public boolean equals( Object o )
94+
{
95+
if ( this == o )
96+
{
97+
return true;
98+
}
99+
if ( o == null || getClass() != o.getClass() )
100+
{
101+
return false;
102+
}
103+
QueryKeys queryKeys = (QueryKeys) o;
104+
return keys.equals( queryKeys.keys ) && keyIndex.equals( queryKeys.keyIndex );
105+
}
106+
107+
@Override
108+
public int hashCode()
109+
{
110+
return Objects.hash( keys, keyIndex );
111+
}
112+
}

driver/src/test/java/org/neo4j/driver/internal/handlers/RunResponseHandlerTest.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@
2020

2121
import org.junit.jupiter.api.Test;
2222

23+
import java.util.HashMap;
2324
import java.util.List;
25+
import java.util.Map;
2426
import java.util.concurrent.CompletableFuture;
2527

2628
import org.neo4j.driver.internal.messaging.v1.BoltProtocolV1;
@@ -83,7 +85,8 @@ void shouldReturnNoKeysWhenFailed()
8385

8486
handler.onFailure( new RuntimeException() );
8587

86-
assertEquals( emptyList(), handler.queryKeys() );
88+
assertEquals( emptyList(), handler.queryKeys().keys() );
89+
assertEquals( emptyMap(), handler.queryKeys().keyIndex() );
8790
}
8891

8992
@Test
@@ -102,9 +105,14 @@ void shouldReturnKeysWhenSucceeded()
102105
RunResponseHandler handler = newHandler();
103106

104107
List<String> keys = asList( "key1", "key2", "key3" );
108+
Map<String, Integer> keyIndex = new HashMap<>();
109+
keyIndex.put( "key1", 0 );
110+
keyIndex.put( "key2", 1 );
111+
keyIndex.put( "key3", 2 );
105112
handler.onSuccess( singletonMap( "fields", value( keys ) ) );
106113

107-
assertEquals( keys, handler.queryKeys() );
114+
assertEquals( keys, handler.queryKeys().keys() );
115+
assertEquals( keyIndex, handler.queryKeys().keyIndex() );
108116
}
109117

110118
@Test

driver/src/test/java/org/neo4j/driver/internal/reactive/util/ListBasedPullHandler.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.neo4j.driver.internal.handlers.pulln.BasicPullResponseHandler;
2828
import org.neo4j.driver.internal.spi.Connection;
2929
import org.neo4j.driver.internal.util.MetadataExtractor;
30+
import org.neo4j.driver.internal.util.QueryKeys;
3031
import org.neo4j.driver.internal.value.BooleanValue;
3132
import org.neo4j.driver.Record;
3233
import org.neo4j.driver.Value;
@@ -72,7 +73,7 @@ private ListBasedPullHandler( List<Record> list, Throwable error )
7273
if ( list.size() > 1 )
7374
{
7475
Record record = list.get( 0 );
75-
when( super.runResponseHandler.queryKeys() ).thenReturn( record.keys() );
76+
when( super.runResponseHandler.queryKeys() ).thenReturn( new QueryKeys( record.keys() ) );
7677
}
7778
}
7879

driver/src/test/java/org/neo4j/driver/internal/util/MetadataExtractorTest.java

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import org.junit.jupiter.api.Test;
2222

23+
import java.util.HashMap;
2324
import java.util.List;
2425
import java.util.Map;
2526
import java.util.concurrent.TimeUnit;
@@ -77,15 +78,23 @@ class MetadataExtractorTest
7778
void shouldExtractQueryKeys()
7879
{
7980
List<String> keys = asList( "hello", " ", "world", "!" );
80-
List<String> extractedKeys = extractor.extractQueryKeys( singletonMap( "fields", value( keys ) ) );
81-
assertEquals( keys, extractedKeys );
81+
Map<String, Integer> keyIndex = new HashMap<>();
82+
keyIndex.put( "hello", 0 );
83+
keyIndex.put( " ", 1 );
84+
keyIndex.put( "world", 2 );
85+
keyIndex.put( "!", 3 );
86+
87+
QueryKeys extracted = extractor.extractQueryKeys( singletonMap( "fields", value( keys ) ) );
88+
assertEquals( keys, extracted.keys() );
89+
assertEquals( keyIndex, extracted.keyIndex() );
8290
}
8391

8492
@Test
8593
void shouldExtractEmptyQueryKeysWhenNoneInMetadata()
8694
{
87-
List<String> extractedKeys = extractor.extractQueryKeys( emptyMap() );
88-
assertEquals( emptyList(), extractedKeys );
95+
QueryKeys extracted = extractor.extractQueryKeys( emptyMap() );
96+
assertEquals( emptyList(), extracted.keys() );
97+
assertEquals( emptyMap(), extracted.keyIndex() );
8998
}
9099

91100
@Test

0 commit comments

Comments
 (0)