|
1 |
| -package com.netflix.hollow.core.read.engine.object; |
2 |
| - |
3 |
| -import static com.netflix.hollow.core.read.engine.object.HollowObjectTypeDataElements.copyRecord; |
4 |
| -import static com.netflix.hollow.core.read.engine.object.HollowObjectTypeDataElements.varLengthSize; |
5 |
| -import static com.netflix.hollow.core.read.engine.object.HollowObjectTypeDataElements.writeNullField; |
| 1 | +package com.netflix.hollow.core.read.engine.list; |
6 | 2 |
|
7 | 3 | import com.netflix.hollow.core.memory.FixedLengthDataFactory;
|
8 |
| -import com.netflix.hollow.core.memory.VariableLengthDataFactory; |
9 | 4 | import com.netflix.hollow.core.memory.encoding.GapEncodedVariableLengthIntegerReader;
|
10 | 5 |
|
11 | 6 |
|
12 | 7 | /**
|
13 |
| - * Join multiple {@code HollowObjectTypeDataElements}s into 1 {@code HollowObjectTypeDataElements}. |
| 8 | + * Join multiple {@code HollowListTypeDataElements}s into 1 {@code HollowListTypeDataElements}. |
14 | 9 | * Ordinals are remapped and corresponding data is copied over.
|
15 | 10 | * The original data elements are not destroyed.
|
16 | 11 | * The no. of passed data elements must be a power of 2.
|
17 | 12 | */
|
18 |
| -class HollowObjectTypeDataElementsJoiner { |
| 13 | +class HollowListTypeDataElementsJoiner { |
19 | 14 |
|
20 |
| - HollowObjectTypeDataElements join(HollowObjectTypeDataElements[] from) { |
| 15 | + HollowListTypeDataElements join(HollowListTypeDataElements[] from) { |
21 | 16 | final int fromMask = from.length - 1;
|
22 | 17 | final int fromOrdinalShift = 31 - Integer.numberOfLeadingZeros(from.length);
|
23 |
| - long[] currentWriteVarLengthDataPointers; |
24 | 18 |
|
25 | 19 | if (from.length<=0 || !((from.length&(from.length-1))==0)) {
|
26 | 20 | throw new IllegalStateException("No. of DataElements to be joined must be a power of 2");
|
27 | 21 | }
|
28 | 22 |
|
29 |
| - HollowObjectTypeDataElements to = new HollowObjectTypeDataElements(from[0].schema, from[0].memoryMode, from[0].memoryRecycler); |
30 |
| - currentWriteVarLengthDataPointers = new long[from[0].schema.numFields()]; |
31 |
| - |
32 |
| - populateStats(to, from); |
| 23 | + HollowListTypeDataElements to = new HollowListTypeDataElements(from[0].memoryMode, from[0].memoryRecycler); |
33 | 24 |
|
34 | 25 | GapEncodedVariableLengthIntegerReader[] fromRemovals = new GapEncodedVariableLengthIntegerReader[from.length];
|
35 | 26 | for (int i=0;i<from.length;i++) {
|
36 | 27 | fromRemovals[i] = from[i].encodedRemovals;
|
37 | 28 | }
|
38 | 29 | to.encodedRemovals = GapEncodedVariableLengthIntegerReader.join(fromRemovals);
|
39 | 30 |
|
40 |
| - for (HollowObjectTypeDataElements elements : from) { |
| 31 | + for (HollowListTypeDataElements elements : from) { |
41 | 32 | if (elements.encodedAdditions != null) {
|
42 | 33 | throw new IllegalStateException("Encountered encodedAdditions in data elements joiner- this is not expected " +
|
43 | 34 | "since encodedAdditions only exist on delta data elements and they dont carry over to target data elements, " +
|
44 | 35 | "delta data elements are never split/joined");
|
45 | 36 | }
|
46 | 37 | }
|
47 | 38 |
|
48 |
| - for(int ordinal=0;ordinal<=to.maxOrdinal;ordinal++) { |
49 |
| - int fromIndex = ordinal & fromMask; |
50 |
| - int fromOrdinal = ordinal >> fromOrdinalShift; |
| 39 | + populateStats(to, from); |
51 | 40 |
|
52 |
| - if (fromOrdinal <= from[fromIndex].maxOrdinal) { |
53 |
| - copyRecord(to, ordinal, from[fromIndex], fromOrdinal, currentWriteVarLengthDataPointers); |
54 |
| - } else { |
55 |
| - // lopsided shards could result for consumers that skip type shards with no additions |
56 |
| - writeNullRecord(to, ordinal, currentWriteVarLengthDataPointers); |
57 |
| - } |
58 |
| - } |
| 41 | + copyRecords(to, from); |
59 | 42 |
|
60 | 43 | return to;
|
61 | 44 | }
|
62 | 45 |
|
63 |
| - private void writeNullRecord(HollowObjectTypeDataElements to, int toOrdinal, long[] currentWriteVarLengthDataPointers) { |
64 |
| - for(int fieldIndex=0;fieldIndex<to.schema.numFields();fieldIndex++) { |
65 |
| - long currentWriteFixedLengthStartBit = ((long)toOrdinal * to.bitsPerRecord) + to.bitOffsetPerField[fieldIndex]; |
66 |
| - writeNullField(to, fieldIndex, currentWriteFixedLengthStartBit, currentWriteVarLengthDataPointers); |
67 |
| - } |
68 |
| - } |
69 |
| - |
70 |
| - void populateStats(HollowObjectTypeDataElements to, HollowObjectTypeDataElements[] from) { |
71 |
| - long[] varLengthSizes = new long[to.schema.numFields()]; |
72 |
| - |
| 46 | + void populateStats(HollowListTypeDataElements to, HollowListTypeDataElements[] from) { |
73 | 47 | to.maxOrdinal = -1;
|
74 | 48 | for(int fromIndex=0;fromIndex<from.length;fromIndex++) {
|
75 |
| - for(int ordinal=0;ordinal<=from[fromIndex].maxOrdinal;ordinal++) { |
76 |
| - for(int fieldIdx=0;fieldIdx<to.schema.numFields();fieldIdx++) { |
77 |
| - if(from[fromIndex].varLengthData[fieldIdx] != null) { |
78 |
| - varLengthSizes[fieldIdx] += varLengthSize(from[fromIndex], ordinal, fieldIdx); |
79 |
| - } |
80 |
| - } |
81 |
| - } |
82 | 49 | int mappedMaxOrdinal = from[fromIndex].maxOrdinal == -1 ? -1 : (from[fromIndex].maxOrdinal * from.length) + fromIndex;
|
83 | 50 | to.maxOrdinal = Math.max(to.maxOrdinal, mappedMaxOrdinal);
|
84 |
| - } |
85 |
| - |
86 |
| - for(int fieldIdx=0;fieldIdx<to.schema.numFields();fieldIdx++) { |
87 |
| - for(int i=0;i<from.length;i++) { |
88 |
| - if(from[i].varLengthData[fieldIdx] != null) { // if any of the join candidates have var len data set for this field |
89 |
| - to.varLengthData[fieldIdx] = VariableLengthDataFactory.get(to.memoryMode, to.memoryRecycler); |
90 |
| - break; |
91 |
| - } |
| 51 | + if (from[fromIndex].bitsPerElement > to.bitsPerElement) { |
| 52 | + // uneven bitsPerElement could be the case for consumers that skip type shards with no additions, so pick max across all shards |
| 53 | + to.bitsPerElement = from[fromIndex].bitsPerElement; |
92 | 54 | }
|
93 | 55 | }
|
94 | 56 |
|
95 |
| - for(int fieldIdx=0;fieldIdx<to.schema.numFields();fieldIdx++) { |
96 |
| - if(to.varLengthData[fieldIdx] == null) { |
97 |
| - // do not assume bitsPerField will be uniform |
98 |
| - for(int fromIndex=0;fromIndex<from.length;fromIndex++) { |
99 |
| - to.bitsPerField[fieldIdx] = Math.max(to.bitsPerField[fieldIdx], from[fromIndex].bitsPerField[fieldIdx]); |
100 |
| - } |
| 57 | + final int fromMask = from.length - 1; |
| 58 | + final int fromOrdinalShift = 31 - Integer.numberOfLeadingZeros(from.length); |
| 59 | + long totalOfListSizes = 0; |
| 60 | + for(int ordinal=0;ordinal<=to.maxOrdinal;ordinal++) { |
| 61 | + int fromIndex = ordinal & fromMask; |
| 62 | + int fromOrdinal = ordinal >> fromOrdinalShift; |
| 63 | + |
| 64 | + long startElement; |
| 65 | + long endElement; |
| 66 | + if (fromOrdinal == 0) { |
| 67 | + startElement = 0; |
| 68 | + endElement = from[fromIndex].listPointerData.getElementValue(0, from[fromIndex].bitsPerListPointer); |
101 | 69 | } else {
|
102 |
| - to.bitsPerField[fieldIdx] = (64 - Long.numberOfLeadingZeros(varLengthSizes[fieldIdx] + 1)) + 1; |
| 70 | + long endFixedLengthOffset = (long)fromOrdinal * from[fromIndex].bitsPerListPointer; |
| 71 | + long startFixedLengthOffset = endFixedLengthOffset - from[fromIndex].bitsPerListPointer; |
| 72 | + startElement = from[fromIndex].listPointerData.getElementValue(startFixedLengthOffset, from[fromIndex].bitsPerListPointer); |
| 73 | + endElement = from[fromIndex].listPointerData.getElementValue(endFixedLengthOffset, from[fromIndex].bitsPerListPointer); |
103 | 74 | }
|
104 |
| - to.nullValueForField[fieldIdx] = to.bitsPerField[fieldIdx] == 64 ? -1L : (1L << to.bitsPerField[fieldIdx]) - 1; |
105 |
| - to.bitOffsetPerField[fieldIdx] = to.bitsPerRecord; |
106 |
| - to.bitsPerRecord += to.bitsPerField[fieldIdx]; |
| 75 | + long numElements = endElement - startElement; |
| 76 | + totalOfListSizes += numElements; |
| 77 | + |
107 | 78 | }
|
108 |
| - to.fixedLengthData = FixedLengthDataFactory.get((long)to.bitsPerRecord * (to.maxOrdinal + 1), to.memoryMode, to.memoryRecycler); |
| 79 | + to.bitsPerListPointer = totalOfListSizes == 0 ? 1 : 64 - Long.numberOfLeadingZeros(totalOfListSizes); |
| 80 | + to.listPointerData = FixedLengthDataFactory.get((long)to.bitsPerListPointer * (to.maxOrdinal + 1), to.memoryMode, to.memoryRecycler); |
| 81 | + to.elementData = FixedLengthDataFactory.get((long)to.bitsPerElement * totalOfListSizes, to.memoryMode, to.memoryRecycler); |
| 82 | + to.totalNumberOfElements = totalOfListSizes; |
| 83 | + } |
| 84 | + |
| 85 | + private void copyRecords(HollowListTypeDataElements to, HollowListTypeDataElements[] from) { |
| 86 | + final int fromMask = from.length - 1; |
| 87 | + final int fromOrdinalShift = 31 - Integer.numberOfLeadingZeros(from.length); |
| 88 | + long elementCounter = 0; |
| 89 | + for(int ordinal=0;ordinal<=to.maxOrdinal;ordinal++) { |
| 90 | + int fromIndex = ordinal & fromMask; |
| 91 | + int fromOrdinal = ordinal >> fromOrdinalShift; |
109 | 92 |
|
110 |
| - // unused |
111 |
| - // to.bitsPerUnfilteredField |
112 |
| - // to.unfilteredFieldIsIncluded |
| 93 | + long startElement; |
| 94 | + long endElement; |
| 95 | + if (fromOrdinal <= from[fromIndex].maxOrdinal) { |
| 96 | + if (fromOrdinal == 0) { |
| 97 | + startElement = 0; |
| 98 | + endElement = from[fromIndex].listPointerData.getElementValue(0, from[fromIndex].bitsPerListPointer); |
| 99 | + } else { |
| 100 | + long endFixedLengthOffset = (long)fromOrdinal * from[fromIndex].bitsPerListPointer; |
| 101 | + long startFixedLengthOffset = endFixedLengthOffset - from[fromIndex].bitsPerListPointer; |
| 102 | + startElement = from[fromIndex].listPointerData.getElementValue(startFixedLengthOffset, from[fromIndex].bitsPerListPointer); |
| 103 | + endElement = from[fromIndex].listPointerData.getElementValue(endFixedLengthOffset, from[fromIndex].bitsPerListPointer); |
| 104 | + } |
| 105 | + for (long element=startElement;element<endElement;element++) { |
| 106 | + int elementOrdinal = (int)from[fromIndex].elementData.getElementValue(element * from[fromIndex].bitsPerElement, from[fromIndex].bitsPerElement); |
| 107 | + to.elementData.setElementValue(elementCounter * to.bitsPerElement, to.bitsPerElement, elementOrdinal); |
| 108 | + elementCounter++; |
| 109 | + } |
| 110 | + } // else: lopsided shards could result for consumers that skip type shards with no additions, that gets handled |
| 111 | + // by not writing anything to elementData, and writing the cached value of elementCounter to listPointerData |
| 112 | + // SNAP: TODO: write a test for lopsided list shards |
| 113 | + |
| 114 | + to.listPointerData.setElementValue(to.bitsPerListPointer * ordinal, to.bitsPerListPointer, elementCounter); |
| 115 | + } |
113 | 116 | }
|
114 | 117 | }
|
0 commit comments