Jackie-Jiang commented on code in PR #8927:
URL: https://github.com/apache/pinot/pull/8927#discussion_r921634433
##########
pinot-spi/src/main/java/org/apache/pinot/spi/data/FieldSpec.java:
##########
@@ -191,6 +193,16 @@ public void setDefaultNullValue(@Nullable Object
defaultNullValue) {
}
}
+ private static final Map<DataType, Object> FIELD_TYPE_DEFAULT_NULL_VALUE_MAP
= new HashMap<>();
+ public static Object getDefaultDimensionNullValue(DataType dataType) {
Review Comment:
(minor) Rename to `getDefaultNullValue(DataType dataType)`
##########
pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/IntermediateResultsBlock.java:
##########
@@ -80,45 +83,64 @@ public IntermediateResultsBlock() {
/**
* Constructor for selection result.
*/
- public IntermediateResultsBlock(DataSchema dataSchema, Collection<Object[]>
selectionResult) {
+ public IntermediateResultsBlock(DataSchema dataSchema, Collection<Object[]>
selectionResult,
+ boolean nullHandlingEnabled) {
_dataSchema = dataSchema;
_selectionResult = selectionResult;
+ _nullHandlingEnabled = nullHandlingEnabled;
}
/**
* Constructor for aggregation result.
* <p>For aggregation only, the result is a list of values.
* <p>For aggregation group-by, the result is a list of maps from group keys
to aggregation values.
*/
- public IntermediateResultsBlock(AggregationFunction[] aggregationFunctions,
List<Object> aggregationResult) {
+ public IntermediateResultsBlock(AggregationFunction[] aggregationFunctions,
List<Object> aggregationResult,
+ boolean nullHandlingEnabled) {
_aggregationFunctions = aggregationFunctions;
_aggregationResult = aggregationResult;
+ _nullHandlingEnabled = nullHandlingEnabled;
+ }
+
+ /**
+ * Constructor for aggregation result.
+ * <p>For aggregation only, the result is a list of values.
+ * <p>For aggregation group-by, the result is a list of maps from group keys
to aggregation values.
+ */
+ public IntermediateResultsBlock(AggregationFunction[] aggregationFunctions,
List<Object> aggregationResult,
Review Comment:
Do we need this extra constructor? For `DistinctOperator`, it can reuse the
constructor without the data schema
##########
pinot-spi/src/main/java/org/apache/pinot/spi/utils/CommonConstants.java:
##########
@@ -272,6 +272,7 @@ public static class QueryOptionKey {
public static final String NUM_REPLICA_GROUPS_TO_QUERY =
"numReplicaGroupsToQuery";
public static final String EXPLAIN_PLAN_VERBOSE = "explainPlanVerbose";
public static final String USE_MULTISTAGE_ENGINE =
"useMultistageEngine";
+ public static final String NULL_HANDLING_ENABLED =
"nullHandlingEnabled";
Review Comment:
```suggestion
public static final String ENABLE_NULL_HANDLING =
"enableNullHandling";
```
##########
pinot-spi/src/main/java/org/apache/pinot/spi/data/FieldSpec.java:
##########
@@ -191,6 +193,16 @@ public void setDefaultNullValue(@Nullable Object
defaultNullValue) {
}
}
+ private static final Map<DataType, Object> FIELD_TYPE_DEFAULT_NULL_VALUE_MAP
= new HashMap<>();
+ public static Object getDefaultDimensionNullValue(DataType dataType) {
+ if (!FIELD_TYPE_DEFAULT_NULL_VALUE_MAP.containsKey(dataType)) {
Review Comment:
This is not thread safe. Suggest moving the logic into a separate util
class, and setup the values for each data type in a `static` block to avoid the
race condition
##########
pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/BaseRawDoubleSingleColumnDistinctExecutor.java:
##########
@@ -39,13 +39,17 @@ abstract class BaseRawDoubleSingleColumnDistinctExecutor
implements DistinctExec
final ExpressionContext _expression;
final DataType _dataType;
final int _limit;
+ final boolean _nullHandlingEnabled;
final DoubleSet _valueSet;
+ int _numNulls;
Review Comment:
Make it `private`
It is a little bit confusing to call it `_numNulls`. Suggest making it
`boolean _hasNull` and keep a separate variable to track the limit
##########
pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawDoubleSingleColumnDistinctOnlyExecutor.java:
##########
@@ -40,10 +42,15 @@ public boolean process(TransformBlock transformBlock) {
int numDocs = transformBlock.getNumDocs();
if (blockValueSet.isSingleValue()) {
double[] values = blockValueSet.getDoubleValuesSV();
+ RoaringBitmap nullBitmap = blockValueSet.getNullBitmap();
Review Comment:
With null handling checked, seems we don't have much code to share
with/without null handling. We can consider having a separate set of classes to
handle the case with null handling enabled?
##########
pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawBigDecimalSingleColumnDistinctOrderByExecutor.java:
##########
@@ -36,22 +37,28 @@ public class
RawBigDecimalSingleColumnDistinctOrderByExecutor extends BaseRawBig
private final PriorityQueue<BigDecimal> _priorityQueue;
public RawBigDecimalSingleColumnDistinctOrderByExecutor(ExpressionContext
expression, DataType dataType,
- OrderByExpressionContext orderByExpression, int limit) {
- super(expression, dataType, limit);
+ OrderByExpressionContext orderByExpression, int limit, boolean
nullHandlingEnabled) {
+ super(expression, dataType, limit, nullHandlingEnabled);
assert orderByExpression.getExpression().equals(expression);
int comparisonFactor = orderByExpression.isAsc() ? -1 : 1;
- _priorityQueue = new ObjectHeapPriorityQueue<>(Math.min(limit,
MAX_INITIAL_CAPACITY),
- (b1, b2) -> b1.compareTo(b2) * comparisonFactor);
+ if (nullHandlingEnabled) {
+ _priorityQueue = new ObjectHeapPriorityQueue<>(Math.min(limit,
MAX_INITIAL_CAPACITY),
+ (b1, b2) -> b1 == null ? (b2 == null ? 0 : 1) : (b2 == null ? -1 :
b1.compareTo(b2)) * comparisonFactor);
+ } else {
+ _priorityQueue = new ObjectHeapPriorityQueue<>(Math.min(limit,
MAX_INITIAL_CAPACITY),
+ (b1, b2) -> b1.compareTo(b2) * comparisonFactor);
+ }
}
@Override
public boolean process(TransformBlock transformBlock) {
BlockValSet blockValueSet = transformBlock.getBlockValueSet(_expression);
BigDecimal[] values = blockValueSet.getBigDecimalValuesSV();
+ RoaringBitmap nullBitmap = blockValueSet.getNullBitmap();
Review Comment:
We need to check if null handling is enabled to avoid the overhead, same for
other classes
##########
pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/DefaultGroupByExecutor.java:
##########
@@ -81,10 +83,11 @@ public DefaultGroupByExecutor(QueryContext queryContext,
ExpressionContext[] gro
// Initialize group key generator
int numGroupsLimit = queryContext.getNumGroupsLimit();
int maxInitialResultHolderCapacity =
queryContext.getMaxInitialResultHolderCapacity();
- if (hasNoDictionaryGroupByExpression) {
+ if (hasNoDictionaryGroupByExpression || _nullHandlingEnabled) {
if (groupByExpressions.length == 1) {
_groupKeyGenerator =
- new NoDictionarySingleColumnGroupKeyGenerator(transformOperator,
groupByExpressions[0], numGroupsLimit);
+ new NoDictionarySingleColumnGroupKeyGenerator(transformOperator,
groupByExpressions[0], numGroupsLimit,
+ _nullHandlingEnabled);
} else {
_groupKeyGenerator =
Review Comment:
Add a TODO to support MV and dictionary based. No dictionary performance is
much worse than dictionary based
##########
pinot-core/src/main/java/org/apache/pinot/core/common/datablock/RowDataBlock.java:
##########
@@ -49,27 +50,25 @@ public RowDataBlock(ByteBuffer byteBuffer)
computeBlockObjectConstants();
}
+ @Nullable
@Override
public RoaringBitmap getNullRowIds(int colId) {
// _fixedSizeData stores two ints per col's null bitmap: offset, and
length.
int position = _numRows * _rowSizeInBytes + colId * Integer.BYTES * 2;
- if (position >= _fixedSizeData.limit()) {
+ if (_fixedSizeData == null || position >= _fixedSizeData.limit()) {
Review Comment:
I don't see the test mentioned above (might be removed?). `_fixedSizeData`
can be `null` for metadata only data table, but we should never read null
bitmap from it
##########
pinot-core/src/main/java/org/apache/pinot/core/query/selection/SelectionOperatorUtils.java:
##########
@@ -92,27 +94,26 @@ public static List<ExpressionContext>
extractExpressions(QueryContext queryConte
}
List<ExpressionContext> selectExpressions =
queryContext.getSelectExpressions();
- if (selectExpressions.size() == 1 &&
selectExpressions.get(0).equals(IDENTIFIER_STAR)) {
- // For 'SELECT *', sort all columns (ignore columns that start with '$')
so that the order is deterministic
- Set<String> allColumns = indexSegment.getColumnNames();
- List<String> selectColumns = new ArrayList<>(allColumns.size());
- for (String column : allColumns) {
- if (column.charAt(0) != '$') {
- selectColumns.add(column);
+ for (ExpressionContext selectExpression : selectExpressions) {
Review Comment:
This is unrelated. Let's make a separate PR for this.
We won't hit this when the schema is present because broker should already
rewrite `*` to actual columns
##########
pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/IntermediateResultsBlock.java:
##########
@@ -80,45 +83,64 @@ public IntermediateResultsBlock() {
/**
* Constructor for selection result.
*/
- public IntermediateResultsBlock(DataSchema dataSchema, Collection<Object[]>
selectionResult) {
+ public IntermediateResultsBlock(DataSchema dataSchema, Collection<Object[]>
selectionResult,
+ boolean nullHandlingEnabled) {
_dataSchema = dataSchema;
_selectionResult = selectionResult;
+ _nullHandlingEnabled = nullHandlingEnabled;
}
/**
* Constructor for aggregation result.
* <p>For aggregation only, the result is a list of values.
* <p>For aggregation group-by, the result is a list of maps from group keys
to aggregation values.
*/
- public IntermediateResultsBlock(AggregationFunction[] aggregationFunctions,
List<Object> aggregationResult) {
+ public IntermediateResultsBlock(AggregationFunction[] aggregationFunctions,
List<Object> aggregationResult,
+ boolean nullHandlingEnabled) {
Review Comment:
This constructor is for aggregation/distinct, and the intermediate result
should never be null. We don't need to handle null in this case
##########
pinot-core/src/main/java/org/apache/pinot/core/util/QueryOptionsUtils.java:
##########
@@ -87,4 +88,9 @@ public static Integer getMinServerGroupTrimSize(Map<String,
String> queryOptions
String minServerGroupTrimSizeString =
queryOptions.get(Request.QueryOptionKey.MIN_SERVER_GROUP_TRIM_SIZE);
return minServerGroupTrimSizeString != null ?
Integer.parseInt(minServerGroupTrimSizeString) : null;
}
+
+ public static boolean isNullHandlingEnabled(Map<String, String>
queryOptions) {
+ return
Boolean.parseBoolean(queryOptions.get(Request.QueryOptionKey.NULL_HANDLING_ENABLED))
Review Comment:
When null handling is enabled, let's put a
`Precondition.checkState(DataTableFactory.getDataTableVersion() >=
DataTableFactory.VERSION_4)` instead of silently disable it
##########
pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/IntermediateResultsBlock.java:
##########
@@ -390,28 +446,55 @@ private DataTable getAggregationResultDataTable()
columnNames[i] = aggregationFunction.getColumnName();
columnDataTypes[i] =
aggregationFunction.getIntermediateResultColumnType();
}
+ RoaringBitmap[] nullBitmaps = null;
Review Comment:
Aggregation intermediate result should never be null, so we shouldn't need
to handling null within this method
##########
pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/IntermediateResultsBlock.java:
##########
@@ -311,16 +334,48 @@ private DataTable getResultDataTable()
throws IOException {
DataTableBuilder dataTableBuilder =
DataTableFactory.getDataTableBuilder(_dataSchema);
ColumnDataType[] storedColumnDataTypes =
_dataSchema.getStoredColumnDataTypes();
+ int numColumns = _dataSchema.size();
Iterator<Record> iterator = _table.iterator();
- while (iterator.hasNext()) {
- Record record = iterator.next();
- dataTableBuilder.startRow();
- int columnIndex = 0;
- for (Object value : record.getValues()) {
- setDataTableColumn(storedColumnDataTypes[columnIndex],
dataTableBuilder, columnIndex, value);
- columnIndex++;
+ RoaringBitmap[] nullBitmaps = null;
Review Comment:
(nit) Can be declared within the if block
##########
pinot-core/src/main/java/org/apache/pinot/core/operator/docvalsets/TransformBlockValSet.java:
##########
@@ -40,12 +44,49 @@
public class TransformBlockValSet implements BlockValSet {
private final ProjectionBlock _projectionBlock;
private final TransformFunction _transformFunction;
+ private final ExpressionContext _expression;
+
+ private boolean _nullBitmapSet;
+ private RoaringBitmap _nullBitmap;
private int[] _numMVEntries;
- public TransformBlockValSet(ProjectionBlock projectionBlock,
TransformFunction transformFunction) {
+ public TransformBlockValSet(ProjectionBlock projectionBlock,
TransformFunction transformFunction,
+ ExpressionContext expression) {
_projectionBlock = projectionBlock;
_transformFunction = transformFunction;
+ _expression = expression;
+ _nullBitmapSet = false;
+ }
+
+ @Nullable
+ @Override
+ public RoaringBitmap getNullBitmap() {
Review Comment:
Please add a todo here to revisit this part in the future because some
transform function can take null input and return non-null result (e.g.
`isNull()`), and we should move this logic into the transform function
##########
pinot-core/src/main/java/org/apache/pinot/core/plan/DistinctPlanNode.java:
##########
@@ -63,14 +64,22 @@ public Operator<IntermediateResultsBlock> run() {
Dictionary dictionary = dataSource.getDictionary();
if (dictionary != null) {
DataSourceMetadata dataSourceMetadata =
dataSource.getDataSourceMetadata();
- return new
DictionaryBasedDistinctOperator(dataSourceMetadata.getDataType(),
distinctAggregationFunction,
- dictionary, dataSourceMetadata.getNumDocs());
+ // If nullHandlingEnabled is set to true, and the column contains
null values, call DistinctOperator instead
+ // of DictionaryBasedDistinctOperator since nullValueVectorReader is
a form of a filter.
+ // TODO: reserve special value in dictionary (e.g. -1) for null in
the future so
+ // DictionaryBasedDistinctOperator can be reused since it is more
efficient than DistinctOperator for
+ // dictionary-encoded columns.
+ NullValueVectorReader nullValueReader =
dataSource.getNullValueVector();
Review Comment:
Check if null handling enabled before other checks. The check can be added
to the if statement on line 60
##########
pinot-core/src/main/java/org/apache/pinot/core/operator/docvalsets/ProjectionBlockValSet.java:
##########
@@ -50,6 +55,30 @@ public ProjectionBlockValSet(DataBlockCache dataBlockCache,
String column, DataS
_dataBlockCache = dataBlockCache;
_column = column;
_dataSource = dataSource;
+ _nullBitmapSet = false;
+ }
+
+ @Nullable
+ @Override
+ public RoaringBitmap getNullBitmap() {
+ if (!_nullBitmapSet) {
+ NullValueVectorReader nullValueReader = _dataSource.getNullValueVector();
+ if (nullValueReader != null &&
nullValueReader.getNullBitmap().getCardinality() > 0) {
Review Comment:
Cache `nullValueReader.getNullBitmap()` in a local variable and use it to
determine whether the value is `null`. `nullValueReader.isNull()` is quite
expensive because it will construct a new bitmap
##########
pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawDoubleSingleColumnDistinctOnlyExecutor.java:
##########
@@ -40,10 +42,15 @@ public boolean process(TransformBlock transformBlock) {
int numDocs = transformBlock.getNumDocs();
if (blockValueSet.isSingleValue()) {
double[] values = blockValueSet.getDoubleValuesSV();
+ RoaringBitmap nullBitmap = blockValueSet.getNullBitmap();
for (int i = 0; i < numDocs; i++) {
- _valueSet.add(values[i]);
- if (_valueSet.size() >= _limit) {
- return true;
+ if (nullBitmap != null && nullBitmap.contains(i)) {
+ _numNulls = 1;
+ } else {
+ _valueSet.add(values[i]);
+ if (_valueSet.size() >= _limit - _numNulls) {
+ return true;
+ }
}
}
} else {
Review Comment:
For null MV value, we want to skip the entry
##########
pinot-core/src/main/java/org/apache/pinot/core/operator/docvalsets/ProjectionBlockValSet.java:
##########
@@ -50,6 +55,30 @@ public ProjectionBlockValSet(DataBlockCache dataBlockCache,
String column, DataS
_dataBlockCache = dataBlockCache;
_column = column;
_dataSource = dataSource;
+ _nullBitmapSet = false;
Review Comment:
(nit) redundant
##########
pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/TransformFunction.java:
##########
@@ -66,6 +67,11 @@ public interface TransformFunction {
*/
Dictionary getDictionary();
+ /**
+ * Returns null value vector for the column if exists, or {@code null} if
not.
+ */
+ NullValueVectorReader getNullValueVectorReader();
Review Comment:
Let's remove this API for now since it is not used and not sure if this is
the correct way to handle null in transform function
##########
pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/NoDictionarySingleColumnGroupKeyGenerator.java:
##########
@@ -72,47 +78,122 @@ public int getGlobalGroupKeyUpperBound() {
@Override
public void generateKeysForBlock(TransformBlock transformBlock, int[]
groupKeys) {
BlockValSet blockValSet =
transformBlock.getBlockValueSet(_groupByExpression);
+ RoaringBitmap nullBitmap = blockValSet.getNullBitmap();
Review Comment:
This should be under the `nullHandlingEnabled` branch. I feel it is cleaner
if we make 2 helper method, one for existing logic, one for null handling
##########
pinot-core/src/main/java/org/apache/pinot/core/plan/maker/InstancePlanMakerImplV2.java:
##########
@@ -184,18 +184,8 @@ public Plan makeInstancePlan(List<IndexSegment>
indexSegments, QueryContext quer
return new GlobalPlanImplV0(new InstanceResponsePlanNode(combinePlanNode,
indexSegments, fetchContexts));
}
- private void applyQueryOptions(QueryContext queryContext) {
+ private void applyExtraQueryOptions(QueryContext queryContext) {
Review Comment:
Suggest not changing this, see the comments in `QueryContext`
##########
pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/NoDictionarySingleColumnGroupKeyGenerator.java:
##########
@@ -72,47 +78,122 @@ public int getGlobalGroupKeyUpperBound() {
@Override
public void generateKeysForBlock(TransformBlock transformBlock, int[]
groupKeys) {
BlockValSet blockValSet =
transformBlock.getBlockValueSet(_groupByExpression);
+ RoaringBitmap nullBitmap = blockValSet.getNullBitmap();
int numDocs = transformBlock.getNumDocs();
switch (_storedType) {
case INT:
int[] intValues = blockValSet.getIntValuesSV();
+ if (_nullHandlingEnabled) {
+ if (nullBitmap != null && nullBitmap.getCardinality() < numDocs) {
+ for (int i = 0; i < numDocs; i++) {
+ groupKeys[i] = nullBitmap.contains(i) ? getKeyForNullValue() :
getKeyForValue(intValues[i]);
+ }
+ } else if (numDocs > 0) {
+ _groupIdForNullValue = _numGroups++;
+ Arrays.fill(groupKeys, _groupIdForNullValue);
Review Comment:
There might already be an id associated with `null`
```suggestion
Arrays.fill(groupKeys, getKeyForNullValue());
```
##########
pinot-core/src/main/java/org/apache/pinot/core/query/request/context/QueryContext.java:
##########
@@ -135,6 +138,12 @@ private QueryContext(@Nullable String tableName, @Nullable
QueryContext subquery
_limit = limit;
_offset = offset;
_queryOptions = queryOptions;
+ if (_queryOptions != null) {
Review Comment:
Suggest not changing it in this PR for the following reasons:
1. These options only apply to server but not broker and we don't want to
pay the overhead on the broker
2. We may add some query options on the fly which won't be applied here
3. Parsing all the options in the same place is easier to track
Since `nullHandlingEnabled` is needed on both broker and server side, we may
call the setter in 2 places
##########
pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawStringSingleColumnDistinctOrderByExecutor.java:
##########
@@ -50,8 +56,9 @@ public boolean process(TransformBlock transformBlock) {
int numDocs = transformBlock.getNumDocs();
if (blockValueSet.isSingleValue()) {
String[] values = blockValueSet.getStringValuesSV();
+ RoaringBitmap nullBitmap = blockValueSet.getNullBitmap();
Review Comment:
Check `nullHandlingEnabled`
##########
pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/NoDictionarySingleColumnGroupKeyGenerator.java:
##########
@@ -51,16 +53,20 @@ public class NoDictionarySingleColumnGroupKeyGenerator
implements GroupKeyGenera
private final DataType _storedType;
private final Map _groupKeyMap;
private final int _globalGroupIdUpperBound;
+ private final boolean _nullHandlingEnabled;
Review Comment:
Most of the logic are not sharable, so we can consider making a base
implementation, then 2 separate class, one for null enabled, one for disabled
##########
pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawStringSingleColumnDistinctOnlyExecutor.java:
##########
@@ -40,7 +42,11 @@ public boolean process(TransformBlock transformBlock) {
int numDocs = transformBlock.getNumDocs();
if (blockValueSet.isSingleValue()) {
String[] values = blockValueSet.getStringValuesSV();
+ RoaringBitmap nullBitmap = blockValueSet.getNullBitmap();
Review Comment:
Check `nullHandlingEnabled`
##########
pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/NoDictionarySingleColumnGroupKeyGenerator.java:
##########
@@ -72,47 +78,122 @@ public int getGlobalGroupKeyUpperBound() {
@Override
public void generateKeysForBlock(TransformBlock transformBlock, int[]
groupKeys) {
BlockValSet blockValSet =
transformBlock.getBlockValueSet(_groupByExpression);
+ RoaringBitmap nullBitmap = blockValSet.getNullBitmap();
int numDocs = transformBlock.getNumDocs();
switch (_storedType) {
case INT:
int[] intValues = blockValSet.getIntValuesSV();
+ if (_nullHandlingEnabled) {
+ if (nullBitmap != null && nullBitmap.getCardinality() < numDocs) {
+ for (int i = 0; i < numDocs; i++) {
+ groupKeys[i] = nullBitmap.contains(i) ? getKeyForNullValue() :
getKeyForValue(intValues[i]);
+ }
+ } else if (numDocs > 0) {
Review Comment:
We should also specialize the case when null bitmap is empty
##########
pinot-core/src/main/java/org/apache/pinot/core/query/reduce/GroupByDataTableReducer.java:
##########
@@ -51,13 +51,17 @@
import org.apache.pinot.core.transport.ServerRoutingInstance;
import org.apache.pinot.core.util.GroupByUtils;
import org.apache.pinot.core.util.trace.TraceRunnable;
+import org.roaringbitmap.RoaringBitmap;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
* Helper class to reduce data tables and set group by results into the
BrokerResponseNative
*/
@SuppressWarnings({"rawtypes", "unchecked"})
public class GroupByDataTableReducer implements DataTableReducer {
+ private static final Logger LOGGER =
LoggerFactory.getLogger(GroupByDataTableReducer.class);
Review Comment:
(minor) Seems not used
--
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]