Jackie-Jiang commented on code in PR #20186:
URL: https://github.com/apache/pinot/pull/20186#discussion_r4503961572
##########
pinot-integration-tests/src/test/java/org/apache/pinot/integration/tests/custom/GroupingSetsQueriesTest.java:
##########
@@ -630,6 +634,7 @@ public Object[][] baseAggregationEquivalenceCases() {
{"sum-cube", "SUM(" + LNG + "), COUNT(*)", "CUBE(" + D1 + ", " + D2 +
")", false},
{"distinctcount-groupingsets", "DISTINCTCOUNT(" + LNG + "), COUNT(*)",
"GROUPING SETS ((" + D1 + "), (" + D2 + "), ())", true},
+ {"exprmax-rollup", "EXPR_MAX(" + LNG + ", " + DBL + "), COUNT(*)",
"ROLLUP(" + D1 + ", " + D2 + ")", false},
Review Comment:
The shared custom cluster doesn't enable the EXPR_MIN/EXPR_MAX rewriters,
and they are JVM-wide, so I moved the case instead of enabling them there.
`ExprMinMaxTest.testGroupingSetsBaseAggregationMatchesExpansion` now runs
`ROLLUP(groupByIntColumn, groupByIntColumn2)` with `expr_max` and `expr_min`,
with `groupingSetsBaseAggregation` on and off, and expects the same rows. It
goes through the single-stage servers and broker, so it merges deserialized
`ExprMinMaxObject`s across base groups. I checked that the base-aggregation run
does take the derive path. `GroupingSetsQueriesTest` is back to its master
version.
##########
pinot-segment-local/src/main/java/org/apache/pinot/segment/local/utils/ObjectSerDeUtils.java:
##########
@@ -0,0 +1,1120 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.pinot.segment.local.utils;
+
+import com.clearspring.analytics.stream.cardinality.HyperLogLog;
+import com.clearspring.analytics.stream.cardinality.HyperLogLogPlus;
+import com.clearspring.analytics.stream.cardinality.RegisterSet;
+import com.dynatrace.hash4j.distinctcount.UltraLogLog;
+import it.unimi.dsi.fastutil.doubles.Double2LongMap;
+import it.unimi.dsi.fastutil.doubles.Double2LongOpenHashMap;
+import it.unimi.dsi.fastutil.doubles.DoubleArrayList;
+import it.unimi.dsi.fastutil.doubles.DoubleIterator;
+import it.unimi.dsi.fastutil.doubles.DoubleOpenHashSet;
+import it.unimi.dsi.fastutil.doubles.DoubleSet;
+import it.unimi.dsi.fastutil.floats.Float2LongMap;
+import it.unimi.dsi.fastutil.floats.Float2LongOpenHashMap;
+import it.unimi.dsi.fastutil.floats.FloatArrayList;
+import it.unimi.dsi.fastutil.floats.FloatIterator;
+import it.unimi.dsi.fastutil.floats.FloatOpenHashSet;
+import it.unimi.dsi.fastutil.floats.FloatSet;
+import it.unimi.dsi.fastutil.ints.Int2LongMap;
+import it.unimi.dsi.fastutil.ints.Int2LongOpenHashMap;
+import it.unimi.dsi.fastutil.ints.IntArrayList;
+import it.unimi.dsi.fastutil.ints.IntIterator;
+import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
+import it.unimi.dsi.fastutil.ints.IntSet;
+import it.unimi.dsi.fastutil.longs.Long2LongMap;
+import it.unimi.dsi.fastutil.longs.Long2LongOpenHashMap;
+import it.unimi.dsi.fastutil.longs.LongArrayList;
+import it.unimi.dsi.fastutil.longs.LongIterator;
+import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
+import it.unimi.dsi.fastutil.longs.LongSet;
+import it.unimi.dsi.fastutil.objects.Object2LongMap;
+import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap;
+import it.unimi.dsi.fastutil.objects.ObjectArrayList;
+import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet;
+import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet;
+import java.io.IOException;
+import java.lang.foreign.MemorySegment;
+import java.math.BigDecimal;
+import java.nio.ByteBuffer;
+import java.nio.IntBuffer;
+import java.util.Set;
+import org.apache.datasketches.theta.ThetaSketch;
+import org.apache.datasketches.theta.UpdatableThetaSketch;
+import
org.apache.pinot.segment.local.customobject.tdigest.SerializedTDigestInput;
+import org.apache.pinot.segment.local.customobject.tdigest.TDigest;
+import org.apache.pinot.segment.local.customobject.tdigest.TDigestAccumulator;
+import org.apache.pinot.segment.spi.memory.PinotOutputStream;
+import org.apache.pinot.segment.spi.memory.PrimitiveArrays;
+import org.apache.pinot.spi.utils.BigDecimalUtils;
+import org.apache.pinot.spi.utils.ByteArray;
+import org.apache.pinot.spi.utils.Utf8Utils;
+
+
+/// The codecs of the intermediate results that aggregation functions carry in
`OBJECT` columns, and of the
+/// pre-aggregated values that value aggregators store in `BYTES` columns, one
per type defined in this module or below
+/// it. An aggregation function owns the serialization of its intermediate
result: it serializes and deserializes the
+/// result, with the codec of its type when there is one, and declares as its
own constant the wire type value that
+/// precedes the payload. A value identifies one intermediate result type for
good: a new type takes a value above
+/// every one declared so far, and a value never changes or gets reused once
its type is retired.
+public class ObjectSerDeUtils {
+ private ObjectSerDeUtils() {
+ }
+
+ /// Serializer/De-serializer for a specific type of object.
+ ///
+ /// @param <T> Type of the object
+ public interface ObjectSerDe<T> {
+
+ /// Serializes a value into a byte array.
+ byte[] serialize(T value);
+
+ /// Writes the serialized form of a value to an output stream, which takes
values straight into its pages. The
+ /// default writes the byte array; a codec that writes its value in parts
overrides this to skip the full-size
+ /// array.
+ default void serialize(T value, PinotOutputStream output)
+ throws IOException {
+ output.write(serialize(value));
+ }
+
+ /// De-serializes a value from the remaining bytes of a byte buffer, which
end where the payload ends. Where the
+ /// position is left afterwards depends on the codec.
+ T deserialize(ByteBuffer buffer);
+
+ /// De-serializes a value from a byte array. The default wraps the array,
which costs nothing. A codec whose native
+ /// input is an array overrides this one too, so that an array caller
skips the copy its buffer overload makes.
+ default T deserialize(byte[] bytes) {
+ return deserialize(ByteBuffer.wrap(bytes));
+ }
+ }
+
+ /// Serializer/De-serializer that knows the payload size of a value without
serializing it, so the value can be
+ /// written straight into a buffer reserved for it.
+ ///
+ /// @param <T> Type of the object
+ public interface SizedObjectSerDe<T> extends ObjectSerDe<T> {
+
+ /// Returns the exact payload size of a value. Must not serialize the
value or allocate the payload. The value must
+ /// stay unchanged between sizing and serialization.
+ int getSerializedSize(T value);
+
+ /// Writes the payload at the current position of the big-endian `buffer`,
which has at least
+ /// [#getSerializedSize(Object)] bytes remaining, and advances the
position by the payload size. Only the position
+ /// changes: the buffer is not retained and the value is not mutated.
Supports heap and direct buffers.
+ void serialize(T value, ByteBuffer buffer);
+
+ @Override
+ default byte[] serialize(T value) {
+ byte[] bytes = new byte[getSerializedSize(value)];
+ serialize(value, ByteBuffer.wrap(bytes));
+ return bytes;
+ }
+ }
+
+ public static final SizedObjectSerDe<IntArrayList> INT_ARRAY_LIST_SER_DE =
new SizedObjectSerDe<>() {
+
+ @Override
+ public int getSerializedSize(IntArrayList intArrayList) {
+ return Math.addExact(Integer.BYTES,
Math.multiplyExact(intArrayList.size(), Integer.BYTES));
+ }
+
+ @Override
+ public void serialize(IntArrayList intArrayList, ByteBuffer buffer) {
+ int size = intArrayList.size();
+ buffer.putInt(size);
+ int[] values = intArrayList.elements();
+ for (int i = 0; i < size; i++) {
+ buffer.putInt(values[i]);
+ }
+ }
+
+ @Override
+ public void serialize(IntArrayList intArrayList, PinotOutputStream output)
+ throws IOException {
+ int size = intArrayList.size();
+ output.writeInt(size);
+ output.writeInts(intArrayList.elements(), 0, size);
+ }
+
+ @Override
+ public IntArrayList deserialize(ByteBuffer buffer) {
+ int numValues = buffer.getInt();
+ int[] values = new int[numValues];
Review Comment:
Restored `checkRemaining` in the INT/LONG/FLOAT/DOUBLE list codecs, along
with `testTruncatedAndNegativeCounts`. It now runs once per payload before the
array is allocated, and checks the bound with a `long` multiply. A truncated
payload throws `BufferUnderflowException` again and a negative count
`IllegalArgumentException`, as with #19806.
##########
pinot-segment-spi/src/main/java/org/apache/pinot/segment/spi/memory/PinotOutputStream.java:
##########
@@ -18,112 +18,92 @@
*/
package org.apache.pinot.segment.spi.memory;
-import com.google.common.primitives.Longs;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
+/// A big-endian [DataOutput] with a seekable position and bulk writes of
primitive arrays.
public abstract class PinotOutputStream extends OutputStream implements
DataOutput {
- /// Return the current position in the OutputStream.
- ///
- /// @return current position in bytes from the start of the stream
+ /// Returns the current position, in bytes from the start of the stream.
public abstract long getCurrentOffset();
- /// Seek to a new position in the OutputStream.
+ /// Seeks to a new position.
///
- /// @param newPos the new position to seek to
- /// @throws IllegalArgumentException If the new position is negative
- public abstract void seek(long newPos);
-
- /// Moves the current offset, applying the given change.
- /// @param change the change to apply to the current offset
/// @throws IllegalArgumentException if the new position is negative
- public void moveCurrentOffset(long change) {
- long newOffset = getCurrentOffset() + change;
- seek(newOffset);
- }
+ public abstract void seek(long newPos);
@Override
- public void writeBoolean(boolean v) throws IOException {
+ public void writeBoolean(boolean v)
+ throws IOException {
write(v ? 1 : 0);
}
@Override
- public void writeByte(int v) throws IOException {
+ public void writeByte(int v)
+ throws IOException {
write(v);
}
@Override
- public void writeChar(int v) throws IOException {
+ public abstract void writeShort(int v)
+ throws IOException;
+
+ @Override
+ public void writeChar(int v)
+ throws IOException {
writeShort(v);
}
@Override
- public void writeChars(String s) throws IOException {
- for (int i = 0; i < s.length(); i++) {
- writeChar(s.charAt(i));
- }
- }
+ public abstract void writeInt(int v)
Review Comment:
The break is intentional. `PagedPinotOutputStream` is the only
implementation, and making the primitive and bulk writes abstract means an
implementation can't silently fall back to a slow per-byte default. I listed
the change under Backward incompatibility in the description:
`moveCurrentOffset` is gone, use `seek(getCurrentOffset() + change)`, and
several writes are now abstract, including the new `writeFloats`.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]