From 3ea9d8168b4e64a4f1e3d2a0bbf9ea8840061163 Mon Sep 17 00:00:00 2001 From: azeng Date: Thu, 22 Aug 2024 12:09:36 -0700 Subject: [PATCH] support query exact match for composite key; --- .../com/netflix/hollow/core/util/IntList.java | 30 +++++ .../keyindex/HollowHistoryTypeKeyIndex.java | 113 ++++++++++++------ .../history/HollowHistoryKeyIndexTest.java | 10 +- 3 files changed, 117 insertions(+), 36 deletions(-) diff --git a/hollow/src/main/java/com/netflix/hollow/core/util/IntList.java b/hollow/src/main/java/com/netflix/hollow/core/util/IntList.java index ab91c458fb..8a96d12498 100644 --- a/hollow/src/main/java/com/netflix/hollow/core/util/IntList.java +++ b/hollow/src/main/java/com/netflix/hollow/core/util/IntList.java @@ -17,6 +17,9 @@ package com.netflix.hollow.core.util; import java.util.Arrays; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; /** * A list of primitive ints @@ -106,4 +109,31 @@ public int hashCode() { result = 31 * result + Arrays.hashCode(values); return result; } + + public static Set createSetFromIntList(IntList list) { + if (Objects.isNull(list)) { + return new HashSet<>(); + } + + HashSet result = new HashSet<>(list.size()); + int listSize = list.size(); + for (int i = 0; i < listSize; ++i) { + result.add(list.get(i)); + } + + return result; + } + + public static IntList createIntListFromSet(Set set) { + if (Objects.isNull(set)) { + return new IntList(0); + } + + IntList result = new IntList(set.size()); + for (int value : set) { + result.add(value); + } + + return result; + } } diff --git a/hollow/src/main/java/com/netflix/hollow/tools/history/keyindex/HollowHistoryTypeKeyIndex.java b/hollow/src/main/java/com/netflix/hollow/tools/history/keyindex/HollowHistoryTypeKeyIndex.java index 48e405a26f..e7fc5a72bd 100644 --- a/hollow/src/main/java/com/netflix/hollow/tools/history/keyindex/HollowHistoryTypeKeyIndex.java +++ b/hollow/src/main/java/com/netflix/hollow/tools/history/keyindex/HollowHistoryTypeKeyIndex.java @@ -17,6 +17,7 @@ package com.netflix.hollow.tools.history.keyindex; import static com.netflix.hollow.core.HollowConstants.ORDINAL_NONE; +import static com.netflix.hollow.tools.util.SearchUtils.ESCAPED_MULTI_FIELD_KEY_DELIMITER; import static com.netflix.hollow.tools.util.SearchUtils.MULTI_FIELD_KEY_DELIMITER; import com.netflix.hollow.core.HollowDataset; @@ -31,6 +32,8 @@ import com.netflix.hollow.core.util.RemovedOrdinalIterator; import java.util.Arrays; import java.util.BitSet; +import java.util.Objects; +import java.util.Set; public class HollowHistoryTypeKeyIndex { private final PrimaryKey primaryKey; @@ -175,50 +178,92 @@ public String getKeyDisplayString(int keyOrdinal) { return builder.toString(); } - public IntList queryIndexedFields(final String query) { - IntList matchingKeys = new IntList(); + private void getMatchesForField(int fieldIdx, String strVal, IntList matchingKeys) throws NumberFormatException { + int hashCode = 0; + Object objectToFind = null; + FieldType fieldType = fieldTypes[fieldIdx]; + switch (fieldType) { + case INT: + final int queryInt = Integer.parseInt(strVal); + hashCode = HollowReadFieldUtils.intHashCode(queryInt); + objectToFind = queryInt; + break; + case LONG: + final long queryLong = Long.parseLong(strVal); + hashCode = HollowReadFieldUtils.longHashCode(queryLong); + objectToFind = queryLong; + break; + case STRING: + hashCode = HashCodes.hashCode(strVal); + objectToFind = strVal.replaceAll(ESCAPED_MULTI_FIELD_KEY_DELIMITER, MULTI_FIELD_KEY_DELIMITER); + break; + case DOUBLE: + final double queryDouble = Double.parseDouble(strVal); + hashCode = HollowReadFieldUtils.doubleHashCode(queryDouble); + objectToFind = queryDouble; + break; + case FLOAT: + final float queryFloat = Float.parseFloat(strVal); + hashCode = HollowReadFieldUtils.floatHashCode(queryFloat); + objectToFind = queryFloat; + break; + default: + } + ordinalMapping.addMatches(HashCodes.hashInt(hashCode), objectToFind, fieldIdx, fieldType, matchingKeys); + } - if (!isInitialized) { - return matchingKeys; + // find the exact matches for the given composite key. + private IntList queryIndexedFieldsForCompositeKey(final String[] compositeKeyComponents) { + IntList matchingKeys = new IntList(); + Set resultSet = null; + for (int i = 0; i < compositeKeyComponents.length; ++i) { + String currComponent = compositeKeyComponents[i]; + try { + getMatchesForField(i, currComponent, matchingKeys); + Set keySet = IntList.createSetFromIntList(matchingKeys); + matchingKeys.clear(); + if (keySet.isEmpty()) { + // directly return as we'll not be able to find any exact matches for the given + // composite key. + return new IntList(); + } + if (Objects.isNull(resultSet)) { + resultSet = keySet; + } + else { + resultSet.retainAll(keySet); + } + } catch (NumberFormatException ignore) { + return new IntList(); + } } + return IntList.createIntListFromSet(resultSet); + } + + private IntList queryIndexedFieldsForNonCompositeKey(final String query) { + IntList matchingKeys = new IntList(); for (int i = 0; i < primaryKey.numFields(); i++) { - int hashCode = 0; - Object objectToFind = null; try { - switch (fieldTypes[i]) { - case INT: - final int queryInt = Integer.parseInt(query); - hashCode = HollowReadFieldUtils.intHashCode(queryInt); - objectToFind = queryInt; - break; - case LONG: - final long queryLong = Long.parseLong(query); - hashCode = HollowReadFieldUtils.longHashCode(queryLong); - objectToFind = queryLong; - break; - case STRING: - hashCode = HashCodes.hashCode(query); - objectToFind = query; - break; - case DOUBLE: - final double queryDouble = Double.parseDouble(query); - hashCode = HollowReadFieldUtils.doubleHashCode(queryDouble); - objectToFind = queryDouble; - break; - case FLOAT: - final float queryFloat = Float.parseFloat(query); - hashCode = HollowReadFieldUtils.floatHashCode(queryFloat); - objectToFind = queryFloat; - break; - default: - } - ordinalMapping.addMatches(HashCodes.hashInt(hashCode), objectToFind, i, fieldTypes[i], matchingKeys); + getMatchesForField(i, query, matchingKeys); } catch(NumberFormatException ignore) {} } return matchingKeys; } + public IntList queryIndexedFields(final String query) { + if (!isInitialized) { + return new IntList(); + } + + String[] keyComponents = query.split("(? 1 && keyComponents.length == primaryKey.numFields()) { + return queryIndexedFieldsForCompositeKey(keyComponents); + } else { + return queryIndexedFieldsForNonCompositeKey(query); + } + } + public Object getKeyFieldValue(int keyFieldIdx, int keyOrdinal) { return ordinalMapping.getFieldObject(keyOrdinal, keyFieldIdx, fieldTypes[keyFieldIdx]); } diff --git a/hollow/src/test/java/com/netflix/hollow/tools/history/HollowHistoryKeyIndexTest.java b/hollow/src/test/java/com/netflix/hollow/tools/history/HollowHistoryKeyIndexTest.java index 061519e043..f1b2bf0f7c 100644 --- a/hollow/src/test/java/com/netflix/hollow/tools/history/HollowHistoryKeyIndexTest.java +++ b/hollow/src/test/java/com/netflix/hollow/tools/history/HollowHistoryKeyIndexTest.java @@ -105,8 +105,15 @@ public void extractsAndIndexesKeyRecords() throws IOException { Assert.assertEquals("2.2:two", keyIdx.getKeyDisplayString("A", 1)); - /// query returns all matching keys + assertResults(keyIdx, "A", "1.1:one", 0); + assertResults(keyIdx, "A", "3.3:one", 2); + assertResults(keyIdx, "A", "4.4:four", 3); + assertResults(keyIdx, "A", "5.5:five!", 4); + assertResults(keyIdx, "A", "5.5!:five!" ); + assertResults(keyIdx, "A", "5.5:five!:" ); + assertResults(keyIdx, "A", "5.5:"); + assertResults(keyIdx, "A", "one", 0, 2); assertResults(keyIdx, "A", "two", 1); assertResults(keyIdx, "A", "four", 3); @@ -132,7 +139,6 @@ private void assertResults(HollowHistoryKeyIndex keyIdx, String type, String que Assert.assertEquals(expectedResults.length, actualResults.size()); actualResults.sort(); - for(int i=0;i