/*
* 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.
*/
<@pp.dropOutputFile />
<#list cast.types as type>
<#if type.major == "VarCharDecimalSimple" || type.major == "EmptyStringVarCharDecimalSimple"> <#-- Cast function template for conversion from VarChar to Decimal9, Decimal18 -->
<#if type.major == "VarCharDecimalSimple">
<@pp.changeOutputFile name="/org/apache/drill/exec/expr/fn/impl/gcast/Cast${type.from}${type.to}.java"/>
<#elseif type.major == "EmptyStringVarCharDecimalSimple">
<@pp.changeOutputFile name="/org/apache/drill/exec/expr/fn/impl/gcast/CastEmptyString${type.from}To${type.to}.java"/>
</#if>
<#include "/@includes/license.ftl" />
package org.apache.drill.exec.expr.fn.impl.gcast;
<#include "/@includes/vv_imports.ftl" />
import org.apache.drill.exec.expr.DrillSimpleFunc;
import org.apache.drill.exec.expr.annotations.FunctionTemplate;
import org.apache.drill.exec.expr.annotations.FunctionTemplate.NullHandling;
import org.apache.drill.exec.expr.annotations.Output;
import org.apache.drill.exec.expr.annotations.Param;
import org.apache.drill.exec.expr.holders.*;
import org.apache.drill.exec.record.RecordBatch;
import org.apache.drill.exec.util.DecimalUtility;
import org.apache.drill.exec.expr.annotations.Workspace;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.DrillBuf;
import java.nio.ByteBuffer;
/*
* This class is generated using freemarker and the ${.template_name} template.
*/
@SuppressWarnings("unused")
<#if type.major == "VarCharDecimalSimple">
@FunctionTemplate(name = "cast${type.to?upper_case}",
scope = FunctionTemplate.FunctionScope.SIMPLE,
returnType = FunctionTemplate.ReturnType.DECIMAL_CAST,
nulls = NullHandling.NULL_IF_NULL)
public class Cast${type.from}${type.to} implements DrillSimpleFunc {
<#elseif type.major == "EmptyStringVarCharDecimalSimple">
@FunctionTemplate(name ="castEmptyString${type.from}To${type.to?upper_case}",
scope = FunctionTemplate.FunctionScope.SIMPLE,
returnType = FunctionTemplate.ReturnType.DECIMAL_CAST,
nulls = NullHandling.INTERNAL)
public class CastEmptyString${type.from}To${type.to} implements DrillSimpleFunc {
</#if>
@Param ${type.from}Holder in;
@Param BigIntHolder precision;
@Param BigIntHolder scale;
<#if type.major == "VarCharDecimalSimple">
@Output ${type.to}Holder out;
<#elseif type.major == "EmptyStringVarCharDecimalSimple">
@Output ${type.to}Holder out;
</#if>
public void setup() {
}
public void eval() {
<#if type.major == "EmptyStringVarCharDecimalSimple">
// Check if the input is null or empty string
if(<#if type.from == "NullableVarChar"> in.isSet == 0 || </#if> in.end == in.start) {
out.isSet = 0;
return;
}
out.isSet = 1;
</#if>
// Assign the scale and precision
out.scale = (int) scale.value;
out.precision = (int) precision.value;
int readIndex = in.start;
int endIndex = in.end;
<#if type.major == "VarCharDecimalSimple">
// Check if its an empty string
if (endIndex - readIndex == 0) {
throw new org.apache.drill.common.exceptions.DrillRuntimeException("Empty String, cannot cast to Decimal");
}
</#if>
// Starting position of fractional part
int scaleIndex = -1;
// true if we have a negative sign at the beginning
boolean negative = false;
// Check the first byte for '-'
byte next = (in.buffer.getByte(readIndex));
// If its a negative number
if (next == '-') {
negative = true;
readIndex++;
}
/* Below two fields are used to compute if the precision is sufficient to store
* the scale along with the integer digits in the string
*/
int integerStartIndex = readIndex;
int integerEndIndex = endIndex;
boolean leadingDigitFound = false;
boolean round = false;
int radix = 10;
// Start parsing the digits
while (readIndex < endIndex) {
next = in.buffer.getByte(readIndex++);
if (next == '.') {
scaleIndex = readIndex;
// Integer end index is just before the scale part begins
integerEndIndex = scaleIndex - 1;
// If the number of fractional digits is > scale specified we might have to truncate
if ((scaleIndex + out.scale) < endIndex ) {
endIndex = scaleIndex + out.scale;
round = true;
}
continue;
} else {
// If its not a '.' we expect only numbers
next = (byte) Character.digit(next, radix);
}
if (next == -1) {
// not a valid digit
byte[] buf = new byte[in.end - in.start];
in.buffer.getBytes(in.start, buf, 0, in.end - in.start);
throw new org.apache.drill.common.exceptions.DrillRuntimeException(new String(buf, com.google.common.base.Charsets.UTF_8));
} else if (leadingDigitFound == false) {
if (next == 0) {
// Ignore the leading zeroes while validating if input digits will fit within the given precision
integerStartIndex++;
} else {
leadingDigitFound = true;
}
}
out.value *= radix;
out.value += next;
}
// Check if the provided precision is enough to store the given input
if (((integerEndIndex - integerStartIndex) + out.scale) > out.precision) {
byte[] buf = new byte[in.end - in.start];
in.buffer.getBytes(in.start, buf, 0, in.end - in.start);
throw new org.apache.drill.common.exceptions.DrillRuntimeException("Precision is insufficient for the provided input: " + new String(buf, com.google.common.base.Charsets.UTF_8) + " Precision: " + out.precision +
" Total Digits: " + (out.scale + (integerEndIndex - integerStartIndex)));
// TODO: Use JDK's java.nio.charset.StandardCharsets.UTF_8.
}
// Check if we need to round up
if (round == true) {
next = in.buffer.getByte(endIndex);
next = (byte) Character.digit(next, radix);
if (next == -1) {
// not a valid digit
byte[] buf = new byte[in.end - in.start];
in.buffer.getBytes(in.start, buf, 0, in.end - in.start);
throw new org.apache.drill.common.exceptions.DrillRuntimeException(new String(buf, com.google.common.base.Charsets.UTF_8));
}
if (next > 4) {
out.value++;
}
}
// Number of fractional digits in the input
int fractionalDigits = (scaleIndex == -1) ? 0 : ((endIndex - scaleIndex));
// Pad the number with zeroes if number of fractional digits is less than scale
if (fractionalDigits < scale.value) {
out.value = (${type.javatype}) (org.apache.drill.exec.util.DecimalUtility.adjustScaleMultiply(out.value, (int) (scale.value - fractionalDigits)));
}
// Negate the number if we saw a -ve sign
if (negative == true) {
out.value *= -1;
}
}
}
<#elseif type.major == "VarCharDecimalComplex" || type.major == "EmptyStringVarCharDecimalComplex"> <#-- Cast function template for conversion from VarChar to Decimal28, Decimal38 -->
<#if type.major == "VarCharDecimalComplex">
<@pp.changeOutputFile name="/org/apache/drill/exec/expr/fn/impl/gcast/Cast${type.from}${type.to}.java"/>
<#elseif type.major == "EmptyStringVarCharDecimalComplex">
<@pp.changeOutputFile name="/org/apache/drill/exec/expr/fn/impl/gcast/CastEmptyString${type.from}To${type.to}.java"/>
</#if>
<#include "/@includes/license.ftl" />
package org.apache.drill.exec.expr.fn.impl.gcast;
<#include "/@includes/vv_imports.ftl" />
import org.apache.drill.exec.expr.DrillSimpleFunc;
import org.apache.drill.exec.expr.annotations.FunctionTemplate;
import org.apache.drill.exec.expr.annotations.FunctionTemplate.NullHandling;
import org.apache.drill.exec.expr.annotations.Output;
import org.apache.drill.exec.expr.annotations.Param;
import org.apache.drill.exec.expr.holders.*;
import org.apache.drill.exec.record.RecordBatch;
import org.apache.drill.exec.util.DecimalUtility;
import org.apache.drill.exec.expr.annotations.Workspace;
import io.netty.buffer.ByteBuf;
import java.nio.ByteBuffer;
/*
* This class is generated using freemarker and the ${.template_name} template.
*/
@SuppressWarnings("unused")
<#if type.major == "VarCharDecimalComplex">
@FunctionTemplate(name = "cast${type.to?upper_case}",
scope = FunctionTemplate.FunctionScope.SIMPLE,
returnType = FunctionTemplate.ReturnType.DECIMAL_CAST,
nulls = NullHandling.NULL_IF_NULL)
public class Cast${type.from}${type.to} implements DrillSimpleFunc {
<#elseif type.major == "EmptyStringVarCharDecimalComplex">
@FunctionTemplate(name = "castEmptyString${type.from}To${type.to?upper_case}",
scope = FunctionTemplate.FunctionScope.SIMPLE,
returnType = FunctionTemplate.ReturnType.DECIMAL_CAST,
nulls = NullHandling.INTERNAL)
public class CastEmptyString${type.from}To${type.to} implements DrillSimpleFunc {
</#if>
@Param ${type.from}Holder in;
@Inject DrillBuf buffer;
@Param BigIntHolder precision;
@Param BigIntHolder scale;
<#if type.major == "VarCharDecimalComplex">
@Output ${type.to}Holder out;
<#elseif type.major == "EmptyStringVarCharDecimalComplex">
@Output ${type.to}Holder out;
</#if>
public void setup() {
int size = ${type.arraySize} * (org.apache.drill.exec.util.DecimalUtility.INTEGER_SIZE);
buffer = buffer.reallocIfNeeded(size);
}
public void eval() {
<#if type.major == "EmptyStringVarCharDecimalComplex">
// Check if the input is null or empty string
if(<#if type.from == "NullableVarChar"> in.isSet == 0 || </#if> in.end == in.start) {
out.isSet = 0;
return;
}
out.isSet = 1;
</#if>
out.buffer = buffer;
out.start = 0;
out.scale = (int) scale.value;
out.precision = (int) precision.value;
boolean sign = false;
// Initialize the output buffer
for (int i = 0; i < ${type.arraySize}; i++) {
out.setInteger(i, 0, out.start, out.buffer);
}
int startIndex;
int readIndex = in.start;
int integerDigits = 0;
int fractionalDigits = 0;
int scaleIndex = -1;
int scaleEndIndex = in.end;
byte[] buf1 = new byte[in.end - in.start];
in.buffer.getBytes(in.start, buf1, 0, in.end - in.start);
Byte next = in.buffer.getByte(readIndex);
if (next == '-') {
readIndex++;
sign = true;
}
if (next == '.') {
readIndex++;
scaleIndex = readIndex; // Fractional part starts at the first position
}
<#if type.major == "VarCharDecimalComplex">
// Check if its an empty string
if (in.end - readIndex == 0) {
throw new org.apache.drill.common.exceptions.DrillRuntimeException("Empty String, cannot cast to Decimal");
}
</#if>
// Store start index for the second pass
startIndex = readIndex;
int radix = 10;
boolean leadingDigitFound = false;
boolean round = false;
/* This is the first pass, we get the number of integer digits and based on the provided scale
* we compute which index into the ByteBuf we start storing the integer part of the Decimal
*/
if (scaleIndex == -1) {
while (readIndex < in.end) {
next = in.buffer.getByte(readIndex++);
if (next == '.') {
// We have found the decimal point. we can compute the starting index into the Decimal's bytebuf
scaleIndex = readIndex;
// We may have to truncate fractional part if > scale
if ((in.end - scaleIndex) > out.scale) {
scaleEndIndex = scaleIndex + out.scale;
round = true;
}
break;
}
// If its not a '.' we expect only numbers
next = (byte) Character.digit(next, radix);
if (next == -1) {
// not a valid digit
byte[] buf = new byte[in.end - in.start];
in.buffer.getBytes(in.start, buf, 0, in.end - in.start);
throw new NumberFormatException(new String(buf, com.google.common.base.Charsets.UTF_8));
}
if (leadingDigitFound == false && next != 0) {
leadingDigitFound = true;
}
if (leadingDigitFound == true) {
integerDigits++;
}
}
}
<#-- TODO: Pull out much of this code into something parallel to
ByteFunctionHelpers but for DECIMAL type implementations. -->
/* Based on the number of integer digits computed and the scale throw an
* exception if the provided precision is not sufficient to store the value
*/
if (integerDigits + out.scale > out.precision) {
byte[] buf = new byte[in.end - in.start];
in.buffer.getBytes(in.start, buf, 0, in.end - in.start);
throw new org.apache.drill.common.exceptions.DrillRuntimeException("Precision is insufficient for the provided input: " + new String(buf, com.google.common.base.Charsets.UTF_8) + " Precision: " + out.precision + " Total Digits: " + (out.scale + integerDigits));
<#-- TODO: Revisit message. (Message would be clearer and shorter
as something like "Precision of X digits is insufficient for
the provided input of "XXXXX.XXXXX" (X total digits)." (An
occurrence of "Precision is insufficient for the provided input:
123456789.987654321 Precision: 5 Total Digits: 9" seemed to
mean that 5 post-decimal digits and 9 total digits were allowed.)
-->
}
// Compute the number of slots needed in the ByteBuf to store the integer and fractional part
int scaleRoundedUp = org.apache.drill.exec.util.DecimalUtility.roundUp(out.scale);
int integerRoundedUp = org.apache.drill.exec.util.DecimalUtility.roundUp(integerDigits);
int ndigits = 0;
int decimalBufferIndex = ${type.arraySize} - scaleRoundedUp - 1;
/* Compute the end index of the integer part.
* If we haven't seen a '.' then entire string is integer.
* If we have seen a '.' it ends before the '.'
*/
int integerEndIndex = (scaleIndex == -1) ? (in.end - 1) : (scaleIndex - 2);
// Traverse and extract the integer part
while (integerEndIndex >= startIndex) {
next = in.buffer.getByte(integerEndIndex--);
next = (byte) Character.digit(next, radix);
int value = (((int) org.apache.drill.exec.util.DecimalUtility.getPowerOfTen(ndigits)) * next) + (out.getInteger(decimalBufferIndex, out.start, out.buffer));
out.setInteger(decimalBufferIndex, value, out.start, out.buffer);
ndigits++;
/* We store the entire decimal as base 1 billion values, which has maximum of 9 digits (MAX_DIGITS)
* Once we have stored MAX_DIGITS in a given slot move to the next slot.
*/
if (ndigits >= org.apache.drill.exec.util.DecimalUtility.MAX_DIGITS) {
ndigits = 0;
decimalBufferIndex--;
}
}
// Traverse and extract the fractional part
decimalBufferIndex = (scaleRoundedUp > 0) ? (${type.arraySize} - scaleRoundedUp) : (${type.arraySize} - 1);
ndigits = 0;
if (scaleIndex != -1) {
while (scaleIndex < scaleEndIndex) {
// check if we have scanned MAX_DIGITS and we need to move to the next index
if (ndigits >= org.apache.drill.exec.util.DecimalUtility.MAX_DIGITS) {
ndigits = 0;
decimalBufferIndex++;
}
next = in.buffer.getByte(scaleIndex++);
// We expect only numbers beyond this
next = (byte) Character.digit(next, radix);
if (next == -1) {
// not a valid digit
byte[] buf = new byte[in.end - in.start];
in.buffer.getBytes(in.start, buf, 0, in.end - in.start);
throw new NumberFormatException(new String(buf, com.google.common.base.Charsets.UTF_8));
}
int value = (out.getInteger(decimalBufferIndex, out.start, out.buffer) * radix) + next;
out.setInteger(decimalBufferIndex, value, out.start, out.buffer);
// added another digit to the current index
ndigits++;
}
// round up the decimal if we had to chop off a part of it
if (round == true) {
next = in.buffer.getByte(scaleEndIndex);
// We expect only numbers beyond this
next = (byte) Character.digit(next, radix);
if (next == -1) {
// not a valid digit
byte[] buf = new byte[in.end - in.start];
in.buffer.getBytes(in.start, buf, 0, in.end - in.start);
throw new NumberFormatException(new String(buf, com.google.common.base.Charsets.UTF_8));
}
if (next > 4) {
// Need to round up
out.setInteger(decimalBufferIndex, out.getInteger(decimalBufferIndex, out.start, out.buffer)+1, out.start, out.buffer);
}
}
// Pad zeroes in the fractional part so that number of digits = MAX_DIGITS
if (out.scale > 0) {
int padding = (int) org.apache.drill.exec.util.DecimalUtility.getPowerOfTen((int) (org.apache.drill.exec.util.DecimalUtility.MAX_DIGITS - ndigits));
out.setInteger(decimalBufferIndex, out.getInteger(decimalBufferIndex, out.start, out.buffer) * padding, out.start, out.buffer);
}
int carry = 0;
do {
// propagate the carry
int tempValue = out.getInteger(decimalBufferIndex, out.start, out.buffer) + carry;
if (tempValue >= org.apache.drill.exec.util.DecimalUtility.DIGITS_BASE) {
carry = tempValue / org.apache.drill.exec.util.DecimalUtility.DIGITS_BASE;
tempValue = (tempValue % org.apache.drill.exec.util.DecimalUtility.DIGITS_BASE);
} else {
carry = 0;
}
out.setInteger(decimalBufferIndex--, tempValue, out.start, out.buffer);
} while (carry > 0 && decimalBufferIndex >= 0);
}
out.setSign(sign, out.start, out.buffer);
}
}
</#if> <#-- type.major -->
</#list>