| /** |
| * 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.phoenix.expression.function; |
| |
| import java.util.List; |
| |
| import org.apache.hadoop.hbase.io.ImmutableBytesWritable; |
| import org.apache.phoenix.expression.Expression; |
| import org.apache.phoenix.parse.FunctionParseNode.Argument; |
| import org.apache.phoenix.parse.FunctionParseNode.BuiltInFunction; |
| import org.apache.phoenix.schema.SortOrder; |
| import org.apache.phoenix.schema.tuple.Tuple; |
| import org.apache.phoenix.schema.types.PChar; |
| import org.apache.phoenix.schema.types.PDataType; |
| import org.apache.phoenix.schema.types.PInteger; |
| import org.apache.phoenix.schema.types.PVarchar; |
| import org.apache.phoenix.util.ByteUtil; |
| import org.apache.phoenix.util.StringUtil; |
| |
| /** |
| * Implementation of LPAD(input string, length int [, fill string]) |
| * |
| * Fills up the input to length (number of characters) by prepending characters in fill (space by default). If the input |
| * is already longer than length then it is truncated on the right. |
| */ |
| @BuiltInFunction(name = LpadFunction.NAME, args = { @Argument(allowedTypes = { PVarchar.class }), |
| @Argument(allowedTypes = { PInteger.class }), |
| @Argument(allowedTypes = { PVarchar.class }, defaultValue = "' '") }) |
| public class LpadFunction extends ScalarFunction { |
| public static final String NAME = "LPAD"; |
| |
| public LpadFunction() { |
| } |
| |
| public LpadFunction(List<Expression> children) { |
| super(children); |
| } |
| |
| /** |
| * Helper function to get the utf8 length of CHAR or VARCHAR |
| * |
| * @param ptr |
| * points to the string |
| * @param sortOrder |
| * sortOrder of the string |
| * @param isCharType |
| * whether the string is of char type |
| * @return utf8 length of the string |
| */ |
| private int getUTF8Length(ImmutableBytesWritable ptr, SortOrder sortOrder, boolean isCharType) { |
| return isCharType ? ptr.getLength() : StringUtil.calculateUTF8Length(ptr.get(), ptr.getOffset(), |
| ptr.getLength(), sortOrder); |
| } |
| |
| /** |
| * Helper function to get the byte length of a utf8 encoded string |
| * |
| * @param ptr |
| * points to the string |
| * @param sortOrder |
| * sortOrder of the string |
| * @param isCharType |
| * whether the string is of char type |
| * @return byte length of the string |
| */ |
| private int getSubstringByteLength(ImmutableBytesWritable ptr, int length, SortOrder sortOrder, boolean isCharType) { |
| return isCharType ? length : StringUtil.getByteLengthForUtf8SubStr(ptr.get(), ptr.getOffset(), length, |
| sortOrder); |
| } |
| |
| /** |
| * Left pads a string with with the given fill expression. |
| */ |
| @Override |
| public boolean evaluate(Tuple tuple, ImmutableBytesWritable ptr) { |
| Expression outputStrLenExpr = getOutputStrLenExpr(); |
| if (!outputStrLenExpr.evaluate(tuple, ptr)) { |
| return false; |
| } |
| if (ptr.getLength()==0) { |
| return true; |
| } |
| int outputStrLen = outputStrLenExpr.getDataType().getCodec().decodeInt(ptr, outputStrLenExpr.getSortOrder()); |
| if (outputStrLen < 0) { |
| return false; |
| } |
| |
| Expression strExp = getStrExpr(); |
| if (!strExp.evaluate(tuple, ptr)) { |
| return false; |
| } |
| |
| boolean isStrCharType = getStrExpr().getDataType() == PChar.INSTANCE; |
| boolean isFillCharType = getFillExpr().getDataType() == PChar.INSTANCE; |
| SortOrder strSortOrder = getStrExpr().getSortOrder(); |
| SortOrder fillSortOrder = getFillExpr().getSortOrder(); |
| int inputStrLen = getUTF8Length(ptr, strSortOrder, isStrCharType); |
| |
| if (outputStrLen == inputStrLen) { |
| // nothing to do |
| return true; |
| } |
| if (outputStrLen < inputStrLen) { |
| // truncate the string from the right |
| int subStrByteLength = getSubstringByteLength(ptr, outputStrLen, strSortOrder, isStrCharType); |
| ptr.set(ptr.get(), ptr.getOffset(), subStrByteLength); |
| return true; |
| } |
| |
| // left pad the input string with the fill chars |
| Expression fillExpr = getFillExpr(); |
| ImmutableBytesWritable fillPtr = new ImmutableBytesWritable(); |
| if (!fillExpr.evaluate(tuple, fillPtr)) { |
| return false; |
| } |
| if (fillPtr.getLength()==0) { |
| ptr.set(ByteUtil.EMPTY_BYTE_ARRAY); |
| return true; |
| } |
| |
| // if the padding to be added is not a multiple of the length of the |
| // fill string then we need to use part of the fill string to pad |
| // LPAD(ab, 5, xy) = xyxab |
| // padLen = 3 |
| // numFillsPrepended = 1 |
| // numFillCharsPrepended = 1 |
| |
| // length of the fill string |
| int fillLen = getUTF8Length(fillPtr, fillSortOrder, isFillCharType); |
| // length of padding to be added |
| int padLen = outputStrLen - inputStrLen; |
| // number of fill strings to be prepended |
| int numFillsPrepended = padLen / fillLen; |
| // number of chars from fill string to be prepended |
| int numFillCharsPrepended = padLen % fillLen; |
| |
| // byte length of the input string |
| int strByteLength = ptr.getLength(); |
| // byte length of the fill string |
| int fillByteLength = getSubstringByteLength(fillPtr, fillPtr.getLength(), fillSortOrder, isFillCharType); |
| // byte length of the full fills to be prepended |
| int fullFillsByteLength = numFillsPrepended * fillByteLength; |
| // byte length of the chars of fill string to be prepended |
| int fillCharsByteLength = getSubstringByteLength(fillPtr, numFillCharsPrepended, fillSortOrder, isFillCharType); |
| // byte length of the padded string = |
| int strWithPaddingByteLength = fullFillsByteLength + fillCharsByteLength + strByteLength; |
| |
| // need to invert the fill string if the sort order of fill and |
| // input are different |
| boolean invertFill = fillSortOrder != strSortOrder; |
| byte[] paddedStr = |
| StringUtil.lpad(ptr.get(), ptr.getOffset(), ptr.getLength(), fillPtr.get(), fillPtr.getOffset(), |
| fillPtr.getLength(), invertFill, strWithPaddingByteLength); |
| ptr.set(paddedStr); |
| return true; |
| } |
| |
| @Override |
| public PDataType getDataType() { |
| return PVarchar.INSTANCE; |
| } |
| |
| @Override |
| public String getName() { |
| return NAME; |
| } |
| |
| @Override |
| public SortOrder getSortOrder() { |
| return getStrExpr().getSortOrder(); |
| } |
| |
| private Expression getStrExpr() { |
| return children.get(0); |
| } |
| |
| private Expression getFillExpr() { |
| return children.get(2); |
| } |
| |
| private Expression getOutputStrLenExpr() { |
| return children.get(1); |
| } |
| |
| } |