/*
* Licensed to CRATE Technology GmbH ("Crate") under one or more contributor
* license agreements. See the NOTICE file distributed with this work for
* additional information regarding copyright ownership. Crate 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.
*
* However, if you have executed another commercial license agreement
* with Crate these terms will supersede the license and you may use the
* software solely pursuant to the terms of the relevant commercial agreement.
*/
package io.crate.operation.scalar;
import com.google.common.base.Preconditions;
import io.crate.analyze.symbol.Function;
import io.crate.analyze.symbol.Literal;
import io.crate.analyze.symbol.Symbol;
import io.crate.metadata.*;
import io.crate.data.Input;
import io.crate.types.ArrayType;
import io.crate.types.DataType;
import io.crate.types.DataTypes;
import org.apache.lucene.util.BytesRef;
import java.util.List;
import java.util.Locale;
public abstract class ConcatFunction extends Scalar<BytesRef, BytesRef> {
public static final String NAME = "concat";
private static final BytesRef EMPTY_STRING = new BytesRef("");
private FunctionInfo functionInfo;
public static void register(ScalarFunctionModule module) {
module.register(NAME, new Resolver());
}
ConcatFunction(FunctionInfo functionInfo) {
this.functionInfo = functionInfo;
}
@Override
public FunctionInfo info() {
return functionInfo;
}
@Override
public Symbol normalizeSymbol(Function function, TransactionContext transactionContext) {
if (anyNonLiterals(function.arguments())) {
return function;
}
Input[] inputs = new Input[function.arguments().size()];
for (int i = 0; i < function.arguments().size(); i++) {
inputs[i] = ((Input) function.arguments().get(i));
}
//noinspection unchecked
return Literal.of(functionInfo.returnType(), evaluate(inputs));
}
private static class StringConcatFunction extends ConcatFunction {
StringConcatFunction(FunctionInfo functionInfo) {
super(functionInfo);
}
@Override
public BytesRef evaluate(Input[] args) {
BytesRef firstArg = (BytesRef) args[0].value();
BytesRef secondArg = (BytesRef) args[1].value();
if (firstArg == null) {
if (secondArg == null) {
return EMPTY_STRING;
}
return secondArg;
}
if (secondArg == null) {
return firstArg;
}
byte[] bytes = new byte[firstArg.length + secondArg.length];
System.arraycopy(firstArg.bytes, firstArg.offset, bytes, 0, firstArg.length);
System.arraycopy(secondArg.bytes, secondArg.offset, bytes, firstArg.length, secondArg.length);
return new BytesRef(bytes);
}
}
private static class GenericConcatFunction extends ConcatFunction {
GenericConcatFunction(FunctionInfo functionInfo) {
super(functionInfo);
}
@Override
public BytesRef evaluate(Input[] args) {
BytesRef[] bytesRefs = new BytesRef[args.length];
int numBytes = 0;
for (int i = 0; i < args.length; i++) {
Input input = args[i];
BytesRef value = DataTypes.STRING.value(input.value());
if (value == null) {
value = EMPTY_STRING;
}
bytesRefs[i] = value;
numBytes += value.length;
}
byte[] bytes = new byte[numBytes];
int numWritten = 0;
for (BytesRef bytesRef : bytesRefs) {
System.arraycopy(bytesRef.bytes, bytesRef.offset, bytes, numWritten, bytesRef.length);
numWritten += bytesRef.length;
}
return new BytesRef(bytes);
}
}
private static class Resolver extends BaseFunctionResolver {
protected Resolver() {
super(Signature.withLenientVarArgs(Signature.ArgMatcher.ANY, Signature.ArgMatcher.ANY));
}
@Override
public FunctionImplementation getForTypes(List<DataType> dataTypes) throws IllegalArgumentException {
if (dataTypes.size() == 2 && dataTypes.get(0).equals(DataTypes.STRING) &&
dataTypes.get(1).equals(DataTypes.STRING)) {
return new StringConcatFunction(new FunctionInfo(new FunctionIdent(NAME, dataTypes), DataTypes.STRING));
} else if (dataTypes.size() == 2 && dataTypes.get(0) instanceof ArrayType &&
dataTypes.get(1) instanceof ArrayType) {
DataType innerType0 = ((ArrayType) dataTypes.get(0)).innerType();
DataType innerType1 = ((ArrayType) dataTypes.get(1)).innerType();
Preconditions.checkArgument(
!innerType0.equals(DataTypes.UNDEFINED) || !innerType1.equals(DataTypes.UNDEFINED),
"When concatenating arrays, one of the two arguments can be of undefined inner type, but not both");
if (!innerType0.equals(DataTypes.UNDEFINED)) {
Preconditions.checkArgument(innerType1.isConvertableTo(innerType0),
String.format(Locale.ENGLISH,
"Second argument's inner type (%s) of the array_cat function cannot be converted to the first argument's inner type (%s)",
innerType1, innerType0));
}
return new ArrayCatFunction(ArrayCatFunction.createInfo(dataTypes));
} else {
for (int i = 0; i < dataTypes.size(); i++) {
if (!dataTypes.get(i).isConvertableTo(DataTypes.STRING)) {
throw new IllegalArgumentException(String.format(Locale.ENGLISH,
"Argument %d of the concat function can't be converted to string", i + 1));
}
}
return new GenericConcatFunction(new FunctionInfo(new FunctionIdent(NAME, dataTypes), DataTypes.STRING));
}
}
}
}