Skip to content

Commit a7140bd

Browse files
Merge pull request #696 from Netflix/feature/HollowHistoryCompositeKeySearch
support Composite Key Search for HollowHistory tooling
2 parents 5ee4831 + 3ea9d81 commit a7140bd

File tree

3 files changed

+117
-36
lines changed

3 files changed

+117
-36
lines changed

hollow/src/main/java/com/netflix/hollow/core/util/IntList.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
package com.netflix.hollow.core.util;
1818

1919
import java.util.Arrays;
20+
import java.util.HashSet;
21+
import java.util.Objects;
22+
import java.util.Set;
2023

2124
/**
2225
* A list of primitive ints
@@ -106,4 +109,31 @@ public int hashCode() {
106109
result = 31 * result + Arrays.hashCode(values);
107110
return result;
108111
}
112+
113+
public static Set<Integer> createSetFromIntList(IntList list) {
114+
if (Objects.isNull(list)) {
115+
return new HashSet<>();
116+
}
117+
118+
HashSet<Integer> result = new HashSet<>(list.size());
119+
int listSize = list.size();
120+
for (int i = 0; i < listSize; ++i) {
121+
result.add(list.get(i));
122+
}
123+
124+
return result;
125+
}
126+
127+
public static IntList createIntListFromSet(Set<Integer> set) {
128+
if (Objects.isNull(set)) {
129+
return new IntList(0);
130+
}
131+
132+
IntList result = new IntList(set.size());
133+
for (int value : set) {
134+
result.add(value);
135+
}
136+
137+
return result;
138+
}
109139
}

hollow/src/main/java/com/netflix/hollow/tools/history/keyindex/HollowHistoryTypeKeyIndex.java

Lines changed: 79 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package com.netflix.hollow.tools.history.keyindex;
1818

1919
import static com.netflix.hollow.core.HollowConstants.ORDINAL_NONE;
20+
import static com.netflix.hollow.tools.util.SearchUtils.ESCAPED_MULTI_FIELD_KEY_DELIMITER;
2021
import static com.netflix.hollow.tools.util.SearchUtils.MULTI_FIELD_KEY_DELIMITER;
2122

2223
import com.netflix.hollow.core.HollowDataset;
@@ -31,6 +32,8 @@
3132
import com.netflix.hollow.core.util.RemovedOrdinalIterator;
3233
import java.util.Arrays;
3334
import java.util.BitSet;
35+
import java.util.Objects;
36+
import java.util.Set;
3437

3538
public class HollowHistoryTypeKeyIndex {
3639
private final PrimaryKey primaryKey;
@@ -175,50 +178,92 @@ public String getKeyDisplayString(int keyOrdinal) {
175178
return builder.toString();
176179
}
177180

178-
public IntList queryIndexedFields(final String query) {
179-
IntList matchingKeys = new IntList();
181+
private void getMatchesForField(int fieldIdx, String strVal, IntList matchingKeys) throws NumberFormatException {
182+
int hashCode = 0;
183+
Object objectToFind = null;
184+
FieldType fieldType = fieldTypes[fieldIdx];
185+
switch (fieldType) {
186+
case INT:
187+
final int queryInt = Integer.parseInt(strVal);
188+
hashCode = HollowReadFieldUtils.intHashCode(queryInt);
189+
objectToFind = queryInt;
190+
break;
191+
case LONG:
192+
final long queryLong = Long.parseLong(strVal);
193+
hashCode = HollowReadFieldUtils.longHashCode(queryLong);
194+
objectToFind = queryLong;
195+
break;
196+
case STRING:
197+
hashCode = HashCodes.hashCode(strVal);
198+
objectToFind = strVal.replaceAll(ESCAPED_MULTI_FIELD_KEY_DELIMITER, MULTI_FIELD_KEY_DELIMITER);
199+
break;
200+
case DOUBLE:
201+
final double queryDouble = Double.parseDouble(strVal);
202+
hashCode = HollowReadFieldUtils.doubleHashCode(queryDouble);
203+
objectToFind = queryDouble;
204+
break;
205+
case FLOAT:
206+
final float queryFloat = Float.parseFloat(strVal);
207+
hashCode = HollowReadFieldUtils.floatHashCode(queryFloat);
208+
objectToFind = queryFloat;
209+
break;
210+
default:
211+
}
212+
ordinalMapping.addMatches(HashCodes.hashInt(hashCode), objectToFind, fieldIdx, fieldType, matchingKeys);
213+
}
180214

181-
if (!isInitialized) {
182-
return matchingKeys;
215+
// find the exact matches for the given composite key.
216+
private IntList queryIndexedFieldsForCompositeKey(final String[] compositeKeyComponents) {
217+
IntList matchingKeys = new IntList();
218+
Set<Integer> resultSet = null;
219+
for (int i = 0; i < compositeKeyComponents.length; ++i) {
220+
String currComponent = compositeKeyComponents[i];
221+
try {
222+
getMatchesForField(i, currComponent, matchingKeys);
223+
Set<Integer> keySet = IntList.createSetFromIntList(matchingKeys);
224+
matchingKeys.clear();
225+
if (keySet.isEmpty()) {
226+
// directly return as we'll not be able to find any exact matches for the given
227+
// composite key.
228+
return new IntList();
229+
}
230+
if (Objects.isNull(resultSet)) {
231+
resultSet = keySet;
232+
}
233+
else {
234+
resultSet.retainAll(keySet);
235+
}
236+
} catch (NumberFormatException ignore) {
237+
return new IntList();
238+
}
183239
}
184240

241+
return IntList.createIntListFromSet(resultSet);
242+
}
243+
244+
private IntList queryIndexedFieldsForNonCompositeKey(final String query) {
245+
IntList matchingKeys = new IntList();
185246
for (int i = 0; i < primaryKey.numFields(); i++) {
186-
int hashCode = 0;
187-
Object objectToFind = null;
188247
try {
189-
switch (fieldTypes[i]) {
190-
case INT:
191-
final int queryInt = Integer.parseInt(query);
192-
hashCode = HollowReadFieldUtils.intHashCode(queryInt);
193-
objectToFind = queryInt;
194-
break;
195-
case LONG:
196-
final long queryLong = Long.parseLong(query);
197-
hashCode = HollowReadFieldUtils.longHashCode(queryLong);
198-
objectToFind = queryLong;
199-
break;
200-
case STRING:
201-
hashCode = HashCodes.hashCode(query);
202-
objectToFind = query;
203-
break;
204-
case DOUBLE:
205-
final double queryDouble = Double.parseDouble(query);
206-
hashCode = HollowReadFieldUtils.doubleHashCode(queryDouble);
207-
objectToFind = queryDouble;
208-
break;
209-
case FLOAT:
210-
final float queryFloat = Float.parseFloat(query);
211-
hashCode = HollowReadFieldUtils.floatHashCode(queryFloat);
212-
objectToFind = queryFloat;
213-
break;
214-
default:
215-
}
216-
ordinalMapping.addMatches(HashCodes.hashInt(hashCode), objectToFind, i, fieldTypes[i], matchingKeys);
248+
getMatchesForField(i, query, matchingKeys);
217249
} catch(NumberFormatException ignore) {}
218250
}
219251
return matchingKeys;
220252
}
221253

254+
public IntList queryIndexedFields(final String query) {
255+
if (!isInitialized) {
256+
return new IntList();
257+
}
258+
259+
String[] keyComponents = query.split("(?<!\\\\)" + MULTI_FIELD_KEY_DELIMITER, primaryKey.numFields());
260+
if (keyComponents.length > 1 && keyComponents.length == primaryKey.numFields()) {
261+
return queryIndexedFieldsForCompositeKey(keyComponents);
262+
} else {
263+
return queryIndexedFieldsForNonCompositeKey(query);
264+
}
265+
}
266+
222267
public Object getKeyFieldValue(int keyFieldIdx, int keyOrdinal) {
223268
return ordinalMapping.getFieldObject(keyOrdinal, keyFieldIdx, fieldTypes[keyFieldIdx]);
224269
}

hollow/src/test/java/com/netflix/hollow/tools/history/HollowHistoryKeyIndexTest.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,15 @@ public void extractsAndIndexesKeyRecords() throws IOException {
105105

106106
Assert.assertEquals("2.2:two", keyIdx.getKeyDisplayString("A", 1));
107107

108-
109108
/// query returns all matching keys
109+
assertResults(keyIdx, "A", "1.1:one", 0);
110+
assertResults(keyIdx, "A", "3.3:one", 2);
111+
assertResults(keyIdx, "A", "4.4:four", 3);
112+
assertResults(keyIdx, "A", "5.5:five!", 4);
113+
assertResults(keyIdx, "A", "5.5!:five!" );
114+
assertResults(keyIdx, "A", "5.5:five!:" );
115+
assertResults(keyIdx, "A", "5.5:");
116+
110117
assertResults(keyIdx, "A", "one", 0, 2);
111118
assertResults(keyIdx, "A", "two", 1);
112119
assertResults(keyIdx, "A", "four", 3);
@@ -132,7 +139,6 @@ private void assertResults(HollowHistoryKeyIndex keyIdx, String type, String que
132139
Assert.assertEquals(expectedResults.length, actualResults.size());
133140

134141
actualResults.sort();
135-
136142
for(int i=0;i<expectedResults.length;i++) {
137143
Assert.assertEquals(expectedResults[i], actualResults.get(i));
138144
}

0 commit comments

Comments
 (0)