Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ public abstract class BaseTransformFunction implements TransformFunction {
new TransformResultMetadata(DataType.JSON, true, false);
protected static final TransformResultMetadata BYTES_SV_NO_DICTIONARY_METADATA =
new TransformResultMetadata(DataType.BYTES, true, false);
protected static final TransformResultMetadata UUID_SV_NO_DICTIONARY_METADATA =
new TransformResultMetadata(DataType.UUID, true, false);

protected static final TransformResultMetadata INT_MV_NO_DICTIONARY_METADATA =
new TransformResultMetadata(DataType.INT, false, false);
Expand All @@ -76,6 +78,8 @@ public abstract class BaseTransformFunction implements TransformFunction {
new TransformResultMetadata(DataType.JSON, false, false);
protected static final TransformResultMetadata BYTES_MV_NO_DICTIONARY_METADATA =
new TransformResultMetadata(DataType.BYTES, false, false);
protected static final TransformResultMetadata UUID_MV_NO_DICTIONARY_METADATA =
new TransformResultMetadata(DataType.UUID, false, false);
protected static final TransformResultMetadata UNKNOWN_METADATA =
new TransformResultMetadata(DataType.UNKNOWN, true, false);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/
package org.apache.pinot.core.operator.transform.function;

import com.google.common.base.Preconditions;
import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -97,6 +98,10 @@ public void init(List<TransformFunction> arguments, Map<String, ColumnContext> c
case "VARBINARY":
_resultMetadata = sourceSV ? BYTES_SV_NO_DICTIONARY_METADATA : BYTES_MV_NO_DICTIONARY_METADATA;
break;
case "UUID":
Preconditions.checkState(sourceSV, "Cannot cast from MV to UUID");
_resultMetadata = UUID_SV_NO_DICTIONARY_METADATA;
break;
case "INT_ARRAY":
case "INTEGER_ARRAY":
_resultMetadata = INT_MV_NO_DICTIONARY_METADATA;
Expand All @@ -119,6 +124,13 @@ public void init(List<TransformFunction> arguments, Map<String, ColumnContext> c
case "VARCHAR_ARRAY":
_resultMetadata = STRING_MV_NO_DICTIONARY_METADATA;
break;
case "BYTES_ARRAY":
_resultMetadata = BYTES_MV_NO_DICTIONARY_METADATA;
break;
case "UUID_ARRAY":
Preconditions.checkState(!sourceSV, "Cannot cast from SV to UUID_ARRAY");
_resultMetadata = UUID_MV_NO_DICTIONARY_METADATA;
break;
default:
throw new IllegalArgumentException("Unable to cast expression to type - " + targetType);
}
Expand Down Expand Up @@ -319,13 +331,81 @@ public String[] transformToStringValuesSV(ValueBlock valueBlock) {
byte[][] bytesValues = transformToBytesValuesSV(valueBlock);
ArrayCopyUtils.copy(bytesValues, _stringValuesSV, length);
break;
// Renders a UUID *result* (e.g. CAST(x AS UUID) read as a string). The switch above handles the other
// direction, a UUID *source* cast to STRING.
case UUID:
byte[][] uuidValues = transformToBytesValuesSV(valueBlock);
ArrayCopyUtils.copyFromUuid(uuidValues, _stringValuesSV, length);
break;
default:
throw new IllegalStateException(String.format("Cannot cast from SV %s to STRING", resultDataType));
}
}
return _stringValuesSV;
}

@Override
public byte[][] transformToBytesValuesSV(ValueBlock valueBlock) {
switch (_resultMetadata.getDataType()) {
case BYTES:
return _transformFunction.transformToBytesValuesSV(valueBlock);
case UUID:
return transformToUuidValuesSV(valueBlock);
default:
return super.transformToBytesValuesSV(valueBlock);
}
}

// TODO: Add it to the interface
private byte[][] transformToUuidValuesSV(ValueBlock valueBlock) {
int length = valueBlock.getNumDocs();
initBytesValuesSV(length);
switch (_sourceDataType.getStoredType()) {
case STRING:
String[] stringValues = _transformFunction.transformToStringValuesSV(valueBlock);
ArrayCopyUtils.copyToUuid(stringValues, _bytesValuesSV, length);
break;
case BYTES:
byte[][] bytesValues = _transformFunction.transformToBytesValuesSV(valueBlock);
ArrayCopyUtils.copyToUuid(bytesValues, _bytesValuesSV, length);
break;
default:
throw new IllegalStateException(String.format("Cannot cast from SV %s to UUID", _sourceDataType));
}
return _bytesValuesSV;
}

@Override
public byte[][][] transformToBytesValuesMV(ValueBlock valueBlock) {
switch (_resultMetadata.getDataType()) {
case BYTES:
return _transformFunction.transformToBytesValuesMV(valueBlock);
case UUID:
return transformToUuidValuesMV(valueBlock);
default:
return super.transformToBytesValuesMV(valueBlock);
}
}

// TODO: Add it to the interface
private byte[][][] transformToUuidValuesMV(ValueBlock valueBlock) {
int length = valueBlock.getNumDocs();
initBytesValuesMV(length);
switch (_sourceDataType.getStoredType()) {
case STRING:
String[][] stringValuesMV = _transformFunction.transformToStringValuesMV(valueBlock);
ArrayCopyUtils.copyToUuid(stringValuesMV, _bytesValuesMV, length);
break;
case BYTES:
byte[][][] bytesValuesMV = _transformFunction.transformToBytesValuesMV(valueBlock);
ArrayCopyUtils.copyToUuid(bytesValuesMV, _bytesValuesMV, length);
break;
default:
throw new IllegalStateException(String.format("Cannot cast from MV %s to UUID", _sourceDataType));
}
return _bytesValuesMV;
}

@Override
public int[][] transformToIntValuesMV(ValueBlock valueBlock) {
switch (_resultMetadata.getDataType()) {
Expand Down Expand Up @@ -491,6 +571,11 @@ public String[][] transformToStringValuesMV(ValueBlock valueBlock) {
longValuesMV = transformToTimestampValuesMV(valueBlock);
ArrayCopyUtils.copyFromTimestamp(longValuesMV, _stringValuesMV, length);
break;
// See the SV variant: this renders a UUID result, not a UUID source.
case UUID:
byte[][][] uuidValuesMV = transformToBytesValuesMV(valueBlock);
ArrayCopyUtils.copyFromUuid(uuidValuesMV, _stringValuesMV, length);
break;
default:
throw new IllegalStateException(String.format("Cannot cast from MV %s to STRING", resultDataType));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.ArrayUtils;
Expand Down Expand Up @@ -59,6 +60,7 @@
import org.apache.pinot.spi.utils.BytesUtils;
import org.apache.pinot.spi.utils.JsonUtils;
import org.apache.pinot.spi.utils.ReadMode;
import org.apache.pinot.spi.utils.UuidUtils;
import org.apache.pinot.spi.utils.builder.TableConfigBuilder;
import org.roaringbitmap.RoaringBitmap;
import org.testng.annotations.AfterClass;
Expand All @@ -83,6 +85,7 @@ public abstract class BaseTransformFunctionTest {
protected static final String JSON_STRING_SV_COLUMN = "jsonSV";
protected static final String STRING_SV_NULL_COLUMN = "stringSVNull";
protected static final String BYTES_SV_COLUMN = "bytesSV";
protected static final String UUID_SV_COLUMN = "uuidSV";
protected static final String VECTOR_1_COLUMN = "vector1";
protected static final String VECTOR_2_COLUMN = "vector2";
protected static final String ZERO_VECTOR_COLUMN = "zeroVector";
Expand Down Expand Up @@ -132,6 +135,7 @@ public abstract class BaseTransformFunctionTest {
protected final String[] _jsonArrayValues = new String[NUM_ROWS];
protected final String[] _stringAlphaNumericSVValues = new String[NUM_ROWS];
protected final byte[][] _bytesSVValues = new byte[NUM_ROWS][];
protected final byte[][] _uuidSVValues = new byte[NUM_ROWS][];
protected final int[][] _intMVValues = new int[NUM_ROWS][];
protected final long[][] _longMVValues = new long[NUM_ROWS][];
protected final float[][] _floatMVValues = new float[NUM_ROWS][];
Expand Down Expand Up @@ -181,6 +185,9 @@ public void setUp()
df.format(RANDOM.nextInt() * RANDOM.nextDouble()));
_stringAlphaNumericSVValues[i] = RandomStringUtils.secure().nextAlphanumeric(26);
_bytesSVValues[i] = RandomStringUtils.secure().nextAlphanumeric(26).getBytes();
long mostSignificantBits = (i % 2 == 0) ? Long.MIN_VALUE + i : Long.MAX_VALUE - i;
long leastSignificantBits = ((long) i << Integer.SIZE) | i;
_uuidSVValues[i] = UuidUtils.toBytes(new UUID(mostSignificantBits, leastSignificantBits));

int numValues = 1 + RANDOM.nextInt(MAX_NUM_MULTI_VALUES);
_intMVValues[i] = new int[numValues];
Expand Down Expand Up @@ -251,6 +258,7 @@ public void setUp()
map.put(STRING_ALPHANUM_NULL_SV_COLUMN, _stringAlphaNumericSVValues[i]);
}
map.put(BYTES_SV_COLUMN, _bytesSVValues[i]);
map.put(UUID_SV_COLUMN, _uuidSVValues[i]);

map.put(INT_MV_COLUMN, ArrayUtils.toObject(_intMVValues[i]));
// Same values as INT_MV_COLUMN so callers can compare results against the dict-encoded baseline and
Expand Down Expand Up @@ -304,6 +312,7 @@ public void setUp()
.addSingleValueDimension(STRING_ALPHANUM_SV_COLUMN, FieldSpec.DataType.STRING)
.addSingleValueDimension(STRING_ALPHANUM_NULL_SV_COLUMN, FieldSpec.DataType.STRING)
.addSingleValueDimension(BYTES_SV_COLUMN, FieldSpec.DataType.BYTES)
.addSingleValueDimension(UUID_SV_COLUMN, FieldSpec.DataType.UUID)
.addSingleValueDimension(JSON_COLUMN, FieldSpec.DataType.JSON)
.addSingleValueDimension(DEFAULT_JSON_COLUMN, FieldSpec.DataType.JSON)
.addMultiValueDimension(INT_MV_COLUMN, FieldSpec.DataType.INT)
Expand Down Expand Up @@ -652,8 +661,13 @@ protected void testTransformFunctionWithNull(TransformFunction transformFunction
protected void testTransformFunction(TransformFunction transformFunction, byte[][] expectedValues) {
String[] stringValues = transformFunction.transformToStringValuesSV(_projectionBlock);
byte[][] bytesValues = transformFunction.transformToBytesValuesSV(_projectionBlock);
FieldSpec.DataType resultDataType = transformFunction.getResultMetadata().getDataType();
for (int i = 0; i < NUM_ROWS; i++) {
assertEquals(bytesValues[i], BytesUtils.toBytes(stringValues[i]));
if (resultDataType == FieldSpec.DataType.UUID) {
assertEquals(bytesValues[i], UuidUtils.toBytes(stringValues[i]));
} else {
assertEquals(bytesValues[i], BytesUtils.toBytes(stringValues[i]));
}
assertEquals(bytesValues[i], expectedValues[i]);
}
testNullBitmap(transformFunction, null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@
import org.apache.pinot.common.request.context.RequestContextUtils;
import org.apache.pinot.core.operator.transform.TransformResultMetadata;
import org.apache.pinot.spi.data.FieldSpec;
import org.apache.pinot.spi.exception.BadQueryRequestException;
import org.apache.pinot.spi.utils.ArrayCopyUtils;
import org.apache.pinot.spi.utils.BytesUtils;
import org.apache.pinot.spi.utils.CommonConstants.NullValuePlaceHolder;
import org.apache.pinot.spi.utils.UuidUtils;
import org.roaringbitmap.RoaringBitmap;
Expand All @@ -39,10 +41,12 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;


public class CastTransformFunctionTest extends BaseTransformFunctionTest {
private static final String UUID_VALUE = "550e8400-e29b-41d4-a716-446655440000";
@Test
public void testCastUuidToString() {
String uuid = "550e8400-e29b-41d4-a716-446655440000";
Expand Down Expand Up @@ -331,6 +335,21 @@ public void testCastTransformFunctionMV() {
testTransformFunction(transformFunction, expectedArraySums);
}

@Test
public void testCastTransformFunctionBytesArray() {
ExpressionContext expression =
RequestContextUtils.getExpression(String.format("CAST(%s AS BYTES_ARRAY)", STRING_MV_COLUMN));
TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap);
assertTrue(transformFunction instanceof CastTransformFunction);
TransformResultMetadata resultMetadata = transformFunction.getResultMetadata();
assertEquals(resultMetadata.getDataType(), FieldSpec.DataType.BYTES);
assertFalse(resultMetadata.isSingleValue());

byte[][][] expectedBytesValues = new byte[NUM_ROWS][][];
ArrayCopyUtils.copy(_stringMVValues, expectedBytesValues, NUM_ROWS);
assertEquals(transformFunction.transformToBytesValuesMV(_projectionBlock), expectedBytesValues);
}

@Test
public void testCastNullLiteral() {
ExpressionContext expression = RequestContextUtils.getExpression("cast(null AS INT)");
Expand Down Expand Up @@ -361,4 +380,81 @@ public void testCastNullColumn() {
}
testTransformFunctionWithNull(transformFunction, expectedValues, roaringBitmap);
}

@Test
public void testCastTransformFunctionUUID() {
ExpressionContext expression =
RequestContextUtils.getExpression(String.format("CAST('%s' AS UUID)", UUID_VALUE.toUpperCase()));
TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap);
assertTrue(transformFunction instanceof CastTransformFunction);
assertEquals(transformFunction.getResultMetadata().getDataType(), FieldSpec.DataType.UUID);

byte[][] bytesValues = transformFunction.transformToBytesValuesSV(_projectionBlock);
String[] stringValues = transformFunction.transformToStringValuesSV(_projectionBlock);
byte[] expectedBytes = UuidUtils.toBytes(UUID_VALUE);
for (int i = 0; i < NUM_ROWS; i++) {
assertEquals(bytesValues[i], expectedBytes);
assertEquals(stringValues[i], UUID_VALUE);
}
}

@Test
public void testCastTransformFunctionUuidRoundTrips() {
String bytesHex = BytesUtils.toHexString(UuidUtils.toBytes(UUID_VALUE));

ExpressionContext expression = RequestContextUtils.getExpression(
String.format("CAST(CAST('%s' AS BYTES) AS UUID)", bytesHex));
TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap);
byte[] expectedBytes = UuidUtils.toBytes(UUID_VALUE);
byte[][] expectedUuidValues = new byte[NUM_ROWS][];
for (int i = 0; i < NUM_ROWS; i++) {
expectedUuidValues[i] = expectedBytes;
}
testTransformFunction(transformFunction, expectedUuidValues);

expression = RequestContextUtils.getExpression(String.format("CAST(CAST('%s' AS UUID) AS STRING)", UUID_VALUE));
transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap);
String[] expectedStringValues = new String[NUM_ROWS];
Arrays.fill(expectedStringValues, UUID_VALUE);
testTransformFunction(transformFunction, expectedStringValues);

expression = RequestContextUtils.getExpression(String.format("CAST(CAST('%s' AS UUID) AS BYTES)", UUID_VALUE));
transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap);
byte[][] expectedBytesValues = new byte[NUM_ROWS][];
for (int i = 0; i < NUM_ROWS; i++) {
expectedBytesValues[i] = expectedBytes;
}
testTransformFunction(transformFunction, expectedBytesValues);
}

@Test
public void testCastTransformFunctionUUIDRejectsInvalidLiteral() {
ExpressionContext expression = RequestContextUtils.getExpression("CAST('not-a-uuid' AS UUID)");
TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap);
assertTrue(transformFunction instanceof CastTransformFunction);

IllegalArgumentException exception = Assert.expectThrows(IllegalArgumentException.class,
() -> transformFunction.transformToBytesValuesSV(_projectionBlock));
assertTrue(exception.getMessage().contains("Invalid UUID"));
}

@Test
public void testCastTransformFunctionUUIDRejectsInvalidBytesLiteral() {
ExpressionContext expression = RequestContextUtils.getExpression("CAST(CAST('0011' AS BYTES) AS UUID)");
TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap);
assertTrue(transformFunction instanceof CastTransformFunction);

IllegalArgumentException exception = Assert.expectThrows(IllegalArgumentException.class,
() -> transformFunction.transformToBytesValuesSV(_projectionBlock));
assertTrue(exception.getMessage().contains("Invalid UUID byte length"));
}

@Test
public void testCastTransformFunctionUUIDRejectsMVSource() {
ExpressionContext expression =
RequestContextUtils.getExpression(String.format("CAST(%s AS UUID)", STRING_MV_COLUMN));
BadQueryRequestException exception = Assert.expectThrows(BadQueryRequestException.class,
() -> TransformFunctionFactory.get(expression, _dataSourceMap));
assertTrue(exception.getMessage().contains("Cannot cast from MV to UUID"));
}
}
Loading
Loading