blob: 4c41515b01ccdfee30b80794352ecd6abad85195 [file] [log] [blame]
/*
* 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.asterix.runtime.evaluators.functions.bitwise;
import java.io.IOException;
import org.apache.asterix.common.exceptions.ErrorCode;
import org.apache.asterix.common.exceptions.WarningUtil;
import org.apache.asterix.dataflow.data.nontagged.serde.ABooleanSerializerDeserializer;
import org.apache.asterix.formats.nontagged.SerializerDeserializerProvider;
import org.apache.asterix.om.base.ABoolean;
import org.apache.asterix.om.exceptions.ExceptionUtil;
import org.apache.asterix.om.types.ATypeTag;
import org.apache.asterix.om.types.BuiltinType;
import org.apache.asterix.om.types.EnumDeserializer;
import org.apache.asterix.om.types.hierachy.ATypeHierarchy;
import org.apache.asterix.runtime.evaluators.common.ListAccessor;
import org.apache.asterix.runtime.evaluators.functions.AbstractScalarEval;
import org.apache.asterix.runtime.evaluators.functions.PointableHelper;
import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
import org.apache.hyracks.algebricks.runtime.base.IEvaluatorContext;
import org.apache.hyracks.algebricks.runtime.base.IScalarEvaluator;
import org.apache.hyracks.algebricks.runtime.base.IScalarEvaluatorFactory;
import org.apache.hyracks.api.dataflow.value.ISerializerDeserializer;
import org.apache.hyracks.api.exceptions.HyracksDataException;
import org.apache.hyracks.api.exceptions.IWarningCollector;
import org.apache.hyracks.api.exceptions.SourceLocation;
import org.apache.hyracks.data.std.api.IPointable;
import org.apache.hyracks.data.std.primitive.VoidPointable;
import org.apache.hyracks.data.std.util.ArrayBackedValueStorage;
import org.apache.hyracks.dataflow.common.data.accessors.IFrameTupleReference;
/**
* This function receives two or three arguments, first argument representing the value, second argument representing
* the position to apply the operation at, and third argument (optional) represents a boolean flag. The position can
* be a single value or an array of values. This evaluator is used by the bitwise BITTEST and ISBITSET
* functions.
*
* These functions can be applied only to int64 type numeric values.
*
* The function has the following behavior:
* For the first argument:
* - If the argument is missing or null, then missing or null is returned, respectively.
* - If the argument is int64, then boolean is returned.
* - If the argument is float or double, then boolean or null is returned, depending on the argument value.
* - If the argument is any other type, then null is returned.
*
* For the second argument:
* - If the argument is missing or null, then missing or null is returned, respectively.
* - If the argument is int64, then boolean is returned.
* - If the argument is float or double, then boolean or null is returned, depending on the argument value.
* - If the argument is an array:
* - If the array item type is int64, then int64 or boolean is returned.
* - If the array item type is float or double, then boolean or null is returned, depending on the argument.
* - If the array item type is any other type, then null is returned.
* - If the argument is any other type, then null is returned.
*
* For the third argument:
* - If the argument is missing or null, then missing or null is returned, respectively.
* - If the argument is boolean, then boolean is returned.
* - If the argument is any other type, then null is returned.
*/
class BitValuePositionFlagEvaluator extends AbstractScalarEval {
// Result members
private ABoolean resultBoolean;
private final ArrayBackedValueStorage resultStorage = new ArrayBackedValueStorage();
// This flag has a special purpose. In some cases, when an array of positions is passed, checking
// some arguments might be enough to return the final result, but, instead of stopping, we need to
// continue looping because if any value in the positions array is invalid, we should return a null instead of
// the boolean result, this flag will keep the loop going just for checking the values, while the
// final result is already set previously.
private boolean isStopUpdatingResultBoolean = false;
// Evaluators and Pointables
private final IScalarEvaluator valueEvaluator;
private final IScalarEvaluator positionEvaluator;
private IScalarEvaluator flagEvaluator;
private final IPointable valuePointable = new VoidPointable();
private final IPointable positionPointable = new VoidPointable();
private IPointable flagPointable;
// List accessor
private final ListAccessor listAccessor = new ListAccessor();
private final IPointable listItemPointable = new VoidPointable();
// Serializer/Deserializer
@SuppressWarnings("rawtypes")
private final ISerializerDeserializer aBooleanSerde =
SerializerDeserializerProvider.INSTANCE.getSerializerDeserializer(BuiltinType.ABOOLEAN);
private final IEvaluatorContext context;
private static final byte[] secondArgumentExpectedTypes =
new byte[] { ATypeTag.SERIALIZED_INT64_TYPE_TAG, ATypeTag.SERIALIZED_ORDEREDLIST_TYPE_TAG };
BitValuePositionFlagEvaluator(IEvaluatorContext context, IScalarEvaluatorFactory[] argEvaluatorFactories,
FunctionIdentifier functionIdentifier, SourceLocation sourceLocation) throws HyracksDataException {
super(sourceLocation, functionIdentifier);
this.context = context;
// Evaluators
valueEvaluator = argEvaluatorFactories[0].createScalarEvaluator(context);
positionEvaluator = argEvaluatorFactories[1].createScalarEvaluator(context);
if (argEvaluatorFactories.length > 2) {
flagEvaluator = argEvaluatorFactories[2].createScalarEvaluator(context);
flagPointable = new VoidPointable();
}
}
@SuppressWarnings("unchecked")
@Override
public void evaluate(IFrameTupleReference tuple, IPointable result) throws HyracksDataException {
valueEvaluator.evaluate(tuple, valuePointable);
positionEvaluator.evaluate(tuple, positionPointable);
if (flagEvaluator != null) {
flagEvaluator.evaluate(tuple, flagPointable);
}
if (PointableHelper.checkAndSetMissingOrNull(result, valuePointable, positionPointable, flagPointable)) {
return;
}
// Value argument
byte[] valueBytes = valuePointable.getByteArray();
int valueStartOffset = valuePointable.getStartOffset();
// Type and value validity check
if (!PointableHelper.isValidLongValue(valueBytes, valueStartOffset, true)) {
ExceptionUtil.warnTypeMismatch(context, sourceLoc, functionIdentifier, valueBytes[valueStartOffset], 0,
ATypeTag.BIGINT);
PointableHelper.setNull(result);
return;
}
// Position argument
byte[] positionBytes = positionPointable.getByteArray();
int positionStartOffset = positionPointable.getStartOffset();
ATypeTag positionTypeTag =
EnumDeserializer.ATYPETAGDESERIALIZER.deserialize(positionBytes[positionStartOffset]);
// Type validity check (for position argument, array is a valid type as well)
if (!ATypeHierarchy.canPromote(positionTypeTag, ATypeTag.DOUBLE) && positionTypeTag != ATypeTag.ARRAY) {
ExceptionUtil.warnTypeMismatch(context, sourceLoc, functionIdentifier, positionBytes[positionStartOffset],
1, secondArgumentExpectedTypes);
PointableHelper.setNull(result);
return;
}
// Third argument
boolean isAllSet = false;
isStopUpdatingResultBoolean = false; // Reset the flag to false for each new tuple
if (flagEvaluator != null) {
byte[] flagBytes = flagPointable.getByteArray();
int flagStartOffset = flagPointable.getStartOffset();
ATypeTag flagTypeTag = EnumDeserializer.ATYPETAGDESERIALIZER.deserialize(flagBytes[flagStartOffset]);
if (flagTypeTag != ATypeTag.BOOLEAN) {
ExceptionUtil.warnTypeMismatch(context, sourceLoc, functionIdentifier, flagBytes[flagStartOffset], 2,
ATypeTag.BOOLEAN);
PointableHelper.setNull(result);
return;
}
isAllSet = ABooleanSerializerDeserializer.getBoolean(flagBytes, flagStartOffset + 1);
}
// Result long value
long longResult = ATypeHierarchy.getLongValue(functionIdentifier.getName(), 0, valueBytes, valueStartOffset);
// If any operation returns false, the result should be null
boolean isSuccessfulOperation;
// From here onward, we got valid arguments, so let's handle numeric or array position argument
if (positionTypeTag != ATypeTag.ARRAY) {
isSuccessfulOperation =
applyBitWiseOperationWithNumericAsPosition(longResult, positionBytes, positionStartOffset);
} else {
isSuccessfulOperation =
applyBitWiseOperationWithArrayAsPosition(longResult, positionBytes, positionStartOffset, isAllSet);
}
if (!isSuccessfulOperation) {
PointableHelper.setNull(result);
return;
}
// Write the result
resultStorage.reset();
aBooleanSerde.serialize(resultBoolean, resultStorage.getDataOutput());
result.set(resultStorage);
}
/**
* This method applies the bitwise operation on the int64 value at the specified positions in
* the array.
*
* @param value The long value
* @param bytes bytes of the position
* @param startOffset start offset of the position
* @param isAllSet is AllSet check flag
* @return returns {@code true} if all operations are successful, {@code false} otherwise.
*
* @throws HyracksDataException HyracksDataException
*/
private boolean applyBitWiseOperationWithArrayAsPosition(long value, byte[] bytes, int startOffset,
boolean isAllSet) throws HyracksDataException {
listAccessor.reset(bytes, startOffset);
// In case of an empty array, we return the value as is without change
if (listAccessor.size() == 0) {
return true;
}
try {
// For each item in the array, apply the bitwise operation at that position
for (int i = 0; i < listAccessor.size(); i++) {
listAccessor.getOrWriteItem(i, listItemPointable, resultStorage);
byte[] itemBytes = listItemPointable.getByteArray();
int itemStartOffset = listItemPointable.getStartOffset();
if (!applyBitWiseOperationWithNumericAsPosition(value, itemBytes, itemStartOffset)) {
return false;
}
// Based on the isAllSet flag, we might want to stop re-calculating the result, this
// isStopUpdatingResultBoolean keeps an eye on that.
isStopUpdatingResultBoolean =
!isAllSet ? resultBoolean == ABoolean.TRUE : resultBoolean == ABoolean.FALSE;
}
return true;
} catch (IOException ex) {
throw HyracksDataException.create(ex);
}
}
/**
* This method applies the bitwise operation on the int64 value at the specified position.
*
* @param value the long value
* @param bytes data bytes
* @param startOffset start offset
* @return returns {@code true} if all operations are successful, {@code false} otherwise.
*/
private boolean applyBitWiseOperationWithNumericAsPosition(long value, byte[] bytes, int startOffset)
throws HyracksDataException {
// Value validity check
if (!PointableHelper.isValidLongValue(bytes, startOffset, true)) {
ExceptionUtil.warnTypeMismatch(context, sourceLoc, functionIdentifier, bytes[startOffset], 1,
ATypeTag.BIGINT);
return false;
}
long position = ATypeHierarchy.getLongValue(functionIdentifier.getName(), 1, bytes, startOffset);
// Ensure the position is between 1 and 64 (int64 has 64 bits)
if (position < 1 || position > 64) {
handleOutOfRangeInput(1, 1, 64, position);
return false;
}
// Checks if a certain position is bit 1
if (!isStopUpdatingResultBoolean) {
resultBoolean = (value & (1L << (position - 1))) != 0 ? ABoolean.TRUE : ABoolean.FALSE;
}
return true;
}
private void handleOutOfRangeInput(int inputPosition, int startLimit, int endLimit, long actual) {
IWarningCollector warningCollector = context.getWarningCollector();
if (warningCollector.shouldWarn()) {
warningCollector.warn(WarningUtil.forAsterix(sourceLoc, ErrorCode.VALUE_OUT_OF_RANGE, functionIdentifier,
ExceptionUtil.indexToPosition(inputPosition), startLimit, endLimit, actual));
}
}
}