SQL: Added support for string manipulating functions with more than one parameter (#32356)

Added support for string manipulating functions with more than one parameter:
CONCAT, LEFT, RIGHT, REPEAT, POSITION, LOCATE, REPLACE, SUBSTRING, INSERT
This commit is contained in:
Andrei Stefan 2018-08-01 12:29:06 +03:00 committed by GitHub
parent 2d87287c0d
commit 4c388539a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
55 changed files with 4649 additions and 5 deletions

View File

@ -68,6 +68,15 @@ import org.elasticsearch.xpack.sql.expression.function.scalar.string.Length;
import org.elasticsearch.xpack.sql.expression.function.scalar.string.RTrim;
import org.elasticsearch.xpack.sql.expression.function.scalar.string.Space;
import org.elasticsearch.xpack.sql.expression.function.scalar.string.UCase;
import org.elasticsearch.xpack.sql.expression.function.scalar.string.Concat;
import org.elasticsearch.xpack.sql.expression.function.scalar.string.Insert;
import org.elasticsearch.xpack.sql.expression.function.scalar.string.Left;
import org.elasticsearch.xpack.sql.expression.function.scalar.string.Locate;
import org.elasticsearch.xpack.sql.expression.function.scalar.string.Position;
import org.elasticsearch.xpack.sql.expression.function.scalar.string.Repeat;
import org.elasticsearch.xpack.sql.expression.function.scalar.string.Replace;
import org.elasticsearch.xpack.sql.expression.function.scalar.string.Right;
import org.elasticsearch.xpack.sql.expression.function.scalar.string.Substring;
import org.elasticsearch.xpack.sql.parser.ParsingException;
import org.elasticsearch.xpack.sql.tree.Location;
import org.elasticsearch.xpack.sql.util.StringUtils;
@ -154,6 +163,15 @@ public class FunctionRegistry {
def(LTrim.class, LTrim::new),
def(RTrim.class, RTrim::new),
def(Space.class, Space::new),
def(Concat.class, Concat::new),
def(Insert.class, Insert::new),
def(Left.class, Left::new),
def(Locate.class, Locate::new),
def(Position.class, Position::new),
def(Repeat.class, Repeat::new),
def(Replace.class, Replace::new),
def(Right.class, Right::new),
def(Substring.class, Substring::new),
def(UCase.class, UCase::new),
// Special
def(Score.class, Score::new)));
@ -337,6 +355,47 @@ public class FunctionRegistry {
private interface FunctionBuilder {
Function build(Location location, List<Expression> children, boolean distinct, TimeZone tz);
}
@SuppressWarnings("overloads") // These are ambiguous if you aren't using ctor references but we always do
static <T extends Function> FunctionDefinition def(Class<T> function,
ThreeParametersFunctionBuilder<T> ctorRef, String... aliases) {
FunctionBuilder builder = (location, children, distinct, tz) -> {
boolean isLocateFunction = function.isAssignableFrom(Locate.class);
if (isLocateFunction && (children.size() > 3 || children.size() < 2)) {
throw new IllegalArgumentException("expects two or three arguments");
} else if (!isLocateFunction && children.size() != 3) {
throw new IllegalArgumentException("expects exactly three arguments");
}
if (distinct) {
throw new IllegalArgumentException("does not support DISTINCT yet it was specified");
}
return ctorRef.build(location, children.get(0), children.get(1), children.size() == 3 ? children.get(2) : null);
};
return def(function, builder, false, aliases);
}
interface ThreeParametersFunctionBuilder<T> {
T build(Location location, Expression source, Expression exp1, Expression exp2);
}
@SuppressWarnings("overloads") // These are ambiguous if you aren't using ctor references but we always do
static <T extends Function> FunctionDefinition def(Class<T> function,
FourParametersFunctionBuilder<T> ctorRef, String... aliases) {
FunctionBuilder builder = (location, children, distinct, tz) -> {
if (children.size() != 4) {
throw new IllegalArgumentException("expects exactly four arguments");
}
if (distinct) {
throw new IllegalArgumentException("does not support DISTINCT yet it was specified");
}
return ctorRef.build(location, children.get(0), children.get(1), children.get(2), children.get(3));
};
return def(function, builder, false, aliases);
}
interface FourParametersFunctionBuilder<T> {
T build(Location location, Expression source, Expression exp1, Expression exp2, Expression exp3);
}
private static String normalize(String name) {
// translate CamelCase to camel_case

View File

@ -1,5 +1,5 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
@ -18,6 +18,13 @@ import org.elasticsearch.xpack.sql.expression.function.scalar.processor.runtime.
import org.elasticsearch.xpack.sql.expression.function.scalar.processor.runtime.HitExtractorProcessor;
import org.elasticsearch.xpack.sql.expression.function.scalar.processor.runtime.Processor;
import org.elasticsearch.xpack.sql.expression.function.scalar.string.StringProcessor;
import org.elasticsearch.xpack.sql.expression.function.scalar.string.BinaryStringNumericProcessor;
import org.elasticsearch.xpack.sql.expression.function.scalar.string.BinaryStringStringProcessor;
import org.elasticsearch.xpack.sql.expression.function.scalar.string.ConcatFunctionProcessor;
import org.elasticsearch.xpack.sql.expression.function.scalar.string.InsertFunctionProcessor;
import org.elasticsearch.xpack.sql.expression.function.scalar.string.LocateFunctionProcessor;
import org.elasticsearch.xpack.sql.expression.function.scalar.string.ReplaceFunctionProcessor;
import org.elasticsearch.xpack.sql.expression.function.scalar.string.SubstringFunctionProcessor;
import java.util.ArrayList;
import java.util.List;
@ -49,6 +56,13 @@ public final class Processors {
entries.add(new Entry(Processor.class, MathProcessor.NAME, MathProcessor::new));
// string
entries.add(new Entry(Processor.class, StringProcessor.NAME, StringProcessor::new));
entries.add(new Entry(Processor.class, BinaryStringNumericProcessor.NAME, BinaryStringNumericProcessor::new));
entries.add(new Entry(Processor.class, BinaryStringStringProcessor.NAME, BinaryStringStringProcessor::new));
entries.add(new Entry(Processor.class, ConcatFunctionProcessor.NAME, ConcatFunctionProcessor::new));
entries.add(new Entry(Processor.class, InsertFunctionProcessor.NAME, InsertFunctionProcessor::new));
entries.add(new Entry(Processor.class, LocateFunctionProcessor.NAME, LocateFunctionProcessor::new));
entries.add(new Entry(Processor.class, ReplaceFunctionProcessor.NAME, ReplaceFunctionProcessor::new));
entries.add(new Entry(Processor.class, SubstringFunctionProcessor.NAME, SubstringFunctionProcessor::new));
return entries;
}
}

View File

@ -0,0 +1,96 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.expression.function.scalar.string;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.FieldAttribute;
import org.elasticsearch.xpack.sql.expression.function.scalar.BinaryScalarFunction;
import org.elasticsearch.xpack.sql.expression.function.scalar.script.ScriptTemplate;
import org.elasticsearch.xpack.sql.tree.Location;
import org.elasticsearch.xpack.sql.type.DataType;
import org.elasticsearch.xpack.sql.util.StringUtils;
import java.util.Locale;
import java.util.Objects;
import java.util.function.BiFunction;
import static java.lang.String.format;
import static org.elasticsearch.xpack.sql.expression.function.scalar.script.ParamsBuilder.paramsBuilder;
import static org.elasticsearch.xpack.sql.expression.function.scalar.script.ScriptTemplate.formatTemplate;
/**
* Base class for binary functions that have the first parameter a string, the second parameter a number
* or a string and the result can be a string or a number.
*/
public abstract class BinaryStringFunction<T,R> extends BinaryScalarFunction {
protected BinaryStringFunction(Location location, Expression left, Expression right) {
super(location, left, right);
}
/*
* the operation the binary function handles can receive one String argument, a number or String as second argument
* and it can return a number or a String. The BiFunction below is the base operation for the subsequent implementations.
* T is the second argument, R is the result of applying the operation.
*/
protected abstract BiFunction<String, T, R> operation();
@Override
protected TypeResolution resolveType() {
if (!childrenResolved()) {
return new TypeResolution("Unresolved children");
}
if (!left().dataType().isString()) {
return new TypeResolution("'%s' requires first parameter to be a string type, received %s", functionName(), left().dataType());
}
return resolveSecondParameterInputType(right().dataType());
}
protected abstract TypeResolution resolveSecondParameterInputType(DataType inputType);
@Override
public Object fold() {
@SuppressWarnings("unchecked")
T fold = (T) right().fold();
return operation().apply((String) left().fold(), fold);
}
@Override
protected ScriptTemplate asScriptFrom(ScriptTemplate leftScript, ScriptTemplate rightScript) {
// basically, transform the script to InternalSqlScriptUtils.[function_name](function_or_field1, function_or_field2)
return new ScriptTemplate(format(Locale.ROOT, formatTemplate("{sql}.%s(%s,%s)"),
StringUtils.underscoreToLowerCamelCase(operation().toString()),
leftScript.template(),
rightScript.template()),
paramsBuilder()
.script(leftScript.params()).script(rightScript.params())
.build(), dataType());
}
@Override
protected ScriptTemplate asScriptFrom(FieldAttribute field) {
return new ScriptTemplate(formatScript("doc[{}].value"),
paramsBuilder().variable(field.isInexact() ? field.exactAttribute().name() : field.name()).build(),
dataType());
}
@Override
public int hashCode() {
return Objects.hash(left(), right());
}
@Override
public boolean equals(Object obj) {
if (obj == null || obj.getClass() != getClass()) {
return false;
}
BinaryStringFunction<?,?> other = (BinaryStringFunction<?,?>) obj;
return Objects.equals(other.left(), left())
&& Objects.equals(other.right(), right());
}
}

View File

@ -0,0 +1,32 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.expression.function.scalar.string;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.tree.Location;
import org.elasticsearch.xpack.sql.type.DataType;
/**
* A binary string function with a numeric second parameter and a string result
*/
public abstract class BinaryStringNumericFunction extends BinaryStringFunction<Number, String> {
public BinaryStringNumericFunction(Location location, Expression left, Expression right) {
super(location, left, right);
}
@Override
protected TypeResolution resolveSecondParameterInputType(DataType inputType) {
return inputType.isNumeric() ?
TypeResolution.TYPE_RESOLVED :
new TypeResolution("'%s' requires second parameter to be a numeric type, received %s", functionName(), inputType);
}
@Override
public DataType dataType() {
return DataType.KEYWORD;
}
}

View File

@ -0,0 +1,92 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.expression.function.scalar.string;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.xpack.sql.SqlIllegalArgumentException;
import org.elasticsearch.xpack.sql.expression.function.scalar.processor.runtime.Processor;
import org.elasticsearch.xpack.sql.expression.function.scalar.string.BinaryStringNumericProcessor.BinaryStringNumericOperation;
import java.io.IOException;
import java.util.function.BiFunction;
/**
* Processor class covering string manipulating functions that have the first parameter as string,
* second parameter as numeric and a string result.
*/
public class BinaryStringNumericProcessor extends BinaryStringProcessor<BinaryStringNumericOperation, Number, String> {
public static final String NAME = "sn";
public BinaryStringNumericProcessor(StreamInput in) throws IOException {
super(in, i -> i.readEnum(BinaryStringNumericOperation.class));
}
public BinaryStringNumericProcessor(Processor left, Processor right, BinaryStringNumericOperation operation) {
super(left, right, operation);
}
public enum BinaryStringNumericOperation implements BiFunction<String, Number, String> {
LEFT((s,c) -> {
int i = c.intValue();
if (i < 0) return "";
return i > s.length() ? s : s.substring(0, i);
}),
RIGHT((s,c) -> {
int i = c.intValue();
if (i < 0) return "";
return i > s.length() ? s : s.substring(s.length() - i);
}),
REPEAT((s,c) -> {
int i = c.intValue();
if (i <= 0) return null;
StringBuilder sb = new StringBuilder(s.length() * i);
for (int j = 0; j < i; j++) {
sb.append(s);
}
return sb.toString();
});
BinaryStringNumericOperation(BiFunction<String, Number, String> op) {
this.op = op;
}
private final BiFunction<String, Number, String> op;
@Override
public String apply(String stringExp, Number count) {
return op.apply(stringExp, count);
}
}
@Override
protected void doWrite(StreamOutput out) throws IOException {
out.writeEnum(operation());
}
@Override
protected Object doProcess(Object left, Object right) {
if (left == null || right == null) {
return null;
}
if (!(left instanceof String || left instanceof Character)) {
throw new SqlIllegalArgumentException("A string/char is required; received [{}]", left);
}
if (!(right instanceof Number)) {
throw new SqlIllegalArgumentException("A number is required; received [{}]", right);
}
return operation().apply(left instanceof Character ? left.toString() : (String) left, (Number) right);
}
@Override
public String getWriteableName() {
return NAME;
}
}

View File

@ -0,0 +1,69 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.expression.function.scalar.string;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.function.scalar.processor.definition.BinaryProcessorDefinition;
import org.elasticsearch.xpack.sql.expression.function.scalar.processor.definition.ProcessorDefinition;
import org.elasticsearch.xpack.sql.expression.function.scalar.string.BinaryStringNumericProcessor.BinaryStringNumericOperation;
import org.elasticsearch.xpack.sql.tree.Location;
import org.elasticsearch.xpack.sql.tree.NodeInfo;
import java.util.Objects;
/**
* Processor definition for String operations requiring one string and one numeric argument.
*/
public class BinaryStringNumericProcessorDefinition extends BinaryProcessorDefinition {
private final BinaryStringNumericOperation operation;
public BinaryStringNumericProcessorDefinition(Location location, Expression expression, ProcessorDefinition left,
ProcessorDefinition right, BinaryStringNumericOperation operation) {
super(location, expression, left, right);
this.operation = operation;
}
@Override
protected NodeInfo<BinaryStringNumericProcessorDefinition> info() {
return NodeInfo.create(this, BinaryStringNumericProcessorDefinition::new, expression(), left(), right(), operation());
}
public BinaryStringNumericOperation operation() {
return operation;
}
@Override
protected BinaryProcessorDefinition replaceChildren(ProcessorDefinition newLeft, ProcessorDefinition newRight) {
return new BinaryStringNumericProcessorDefinition(location(), expression(), newLeft, newRight, operation());
}
@Override
public BinaryStringNumericProcessor asProcessor() {
return new BinaryStringNumericProcessor(left().asProcessor(), right().asProcessor(), operation());
}
@Override
public int hashCode() {
return Objects.hash(left(), right(), operation);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
BinaryStringNumericProcessorDefinition other = (BinaryStringNumericProcessorDefinition) obj;
return Objects.equals(operation, other.operation)
&& Objects.equals(left(), other.left())
&& Objects.equals(right(), other.right());
}
}

View File

@ -0,0 +1,59 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.expression.function.scalar.string;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.xpack.sql.expression.function.scalar.processor.runtime.BinaryProcessor;
import org.elasticsearch.xpack.sql.expression.function.scalar.processor.runtime.Processor;
import java.io.IOException;
import java.util.Objects;
import java.util.function.BiFunction;
public abstract class BinaryStringProcessor<O extends Enum<?> & BiFunction<String, T, R>, T, R> extends BinaryProcessor {
private final O operation;
public BinaryStringProcessor(Processor left, Processor right, O operation) {
super(left, right);
this.operation = operation;
}
public BinaryStringProcessor(StreamInput in, Reader<O> reader) throws IOException {
super(in);
operation = reader.read(in);
}
protected O operation() {
return operation;
}
@Override
public int hashCode() {
return Objects.hash(operation);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
BinaryStringProcessor<?,?,?> other = (BinaryStringProcessor<?,?,?>) obj;
return Objects.equals(operation, other.operation)
&& Objects.equals(left(), other.left())
&& Objects.equals(right(), other.right());
}
@Override
public String toString() {
return operation.toString();
}
}

View File

@ -0,0 +1,32 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.expression.function.scalar.string;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.tree.Location;
import org.elasticsearch.xpack.sql.type.DataType;
/**
* A binary string function with two string parameters and a numeric result
*/
public abstract class BinaryStringStringFunction extends BinaryStringFunction<String, Number> {
public BinaryStringStringFunction(Location location, Expression left, Expression right) {
super(location, left, right);
}
@Override
protected TypeResolution resolveSecondParameterInputType(DataType inputType) {
return inputType.isString() ?
TypeResolution.TYPE_RESOLVED :
new TypeResolution("'%s' requires second parameter to be a string type, received %s", functionName(), inputType);
}
@Override
public DataType dataType() {
return DataType.INTEGER;
}
}

View File

@ -0,0 +1,77 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.expression.function.scalar.string;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.xpack.sql.SqlIllegalArgumentException;
import org.elasticsearch.xpack.sql.expression.function.scalar.processor.runtime.Processor;
import org.elasticsearch.xpack.sql.expression.function.scalar.string.BinaryStringStringProcessor.BinaryStringStringOperation;
import java.io.IOException;
import java.util.function.BiFunction;
/**
* Processor class covering string manipulating functions that have two string parameters and a numeric result.
*/
public class BinaryStringStringProcessor extends BinaryStringProcessor<BinaryStringStringOperation, String, Number> {
public static final String NAME = "ss";
public BinaryStringStringProcessor(StreamInput in) throws IOException {
super(in, i -> i.readEnum(BinaryStringStringOperation.class));
}
public BinaryStringStringProcessor(Processor left, Processor right, BinaryStringStringOperation operation) {
super(left, right, operation);
}
public enum BinaryStringStringOperation implements BiFunction<String, String, Number> {
POSITION((sub,str) -> {
if (sub == null || str == null) return null;
int pos = str.indexOf(sub);
return pos < 0 ? 0 : pos+1;
});
BinaryStringStringOperation(BiFunction<String, String, Number> op) {
this.op = op;
}
private final BiFunction<String, String, Number> op;
@Override
public Number apply(String stringExpLeft, String stringExpRight) {
return op.apply(stringExpLeft, stringExpRight);
}
}
@Override
protected void doWrite(StreamOutput out) throws IOException {
out.writeEnum(operation());
}
@Override
protected Object doProcess(Object left, Object right) {
if (left == null || right == null) {
return null;
}
if (!(left instanceof String || left instanceof Character)) {
throw new SqlIllegalArgumentException("A string/char is required; received [{}]", left);
}
if (!(right instanceof String || right instanceof Character)) {
throw new SqlIllegalArgumentException("A string/char is required; received [{}]", right);
}
return operation().apply(left instanceof Character ? left.toString() : (String) left,
right instanceof Character ? right.toString() : (String) right);
}
@Override
public String getWriteableName() {
return NAME;
}
}

View File

@ -0,0 +1,69 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.expression.function.scalar.string;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.function.scalar.processor.definition.BinaryProcessorDefinition;
import org.elasticsearch.xpack.sql.expression.function.scalar.processor.definition.ProcessorDefinition;
import org.elasticsearch.xpack.sql.expression.function.scalar.string.BinaryStringStringProcessor.BinaryStringStringOperation;
import org.elasticsearch.xpack.sql.tree.Location;
import org.elasticsearch.xpack.sql.tree.NodeInfo;
import java.util.Objects;
/**
* Processor definition for String operations requiring two string arguments.
*/
public class BinaryStringStringProcessorDefinition extends BinaryProcessorDefinition {
private final BinaryStringStringOperation operation;
public BinaryStringStringProcessorDefinition(Location location, Expression expression, ProcessorDefinition left,
ProcessorDefinition right, BinaryStringStringOperation operation) {
super(location, expression, left, right);
this.operation = operation;
}
@Override
protected NodeInfo<BinaryStringStringProcessorDefinition> info() {
return NodeInfo.create(this, BinaryStringStringProcessorDefinition::new, expression(), left(), right(), operation);
}
public BinaryStringStringOperation operation() {
return operation;
}
@Override
protected BinaryProcessorDefinition replaceChildren(ProcessorDefinition left, ProcessorDefinition right) {
return new BinaryStringStringProcessorDefinition(location(), expression(), left, right, operation);
}
@Override
public BinaryStringStringProcessor asProcessor() {
return new BinaryStringStringProcessor(left().asProcessor(), right().asProcessor(), operation);
}
@Override
public int hashCode() {
return Objects.hash(left(), right(), operation);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
BinaryStringStringProcessorDefinition other = (BinaryStringStringProcessorDefinition) obj;
return Objects.equals(operation, other.operation)
&& Objects.equals(left(), other.left())
&& Objects.equals(right(), other.right());
}
}

View File

@ -0,0 +1,109 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.expression.function.scalar.string;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.FieldAttribute;
import org.elasticsearch.xpack.sql.expression.function.scalar.BinaryScalarFunction;
import org.elasticsearch.xpack.sql.expression.function.scalar.processor.definition.ProcessorDefinition;
import org.elasticsearch.xpack.sql.expression.function.scalar.processor.definition.ProcessorDefinitions;
import org.elasticsearch.xpack.sql.expression.function.scalar.script.ScriptTemplate;
import org.elasticsearch.xpack.sql.tree.Location;
import org.elasticsearch.xpack.sql.tree.NodeInfo;
import org.elasticsearch.xpack.sql.type.DataType;
import java.util.Locale;
import static java.lang.String.format;
import static org.elasticsearch.xpack.sql.expression.function.scalar.script.ParamsBuilder.paramsBuilder;
import static org.elasticsearch.xpack.sql.expression.function.scalar.script.ScriptTemplate.formatTemplate;
import static org.elasticsearch.xpack.sql.expression.function.scalar.string.ConcatFunctionProcessor.doProcessInScripts;
/**
* Returns a string that is the result of concatenating the two strings received as parameters.
* The result of the function is null only if both parameters are null, otherwise the result is the non-null
* parameter or the concatenation of the two strings if none of them is null.
*/
public class Concat extends BinaryScalarFunction {
public Concat(Location location, Expression source1, Expression source2) {
super(location, source1, source2);
}
protected TypeResolution resolveType() {
if (!childrenResolved()) {
return new TypeResolution("Unresolved children");
}
TypeResolution sourceResolution = StringFunctionUtils.resolveStringInputType(left().dataType(), functionName());
if (sourceResolution != TypeResolution.TYPE_RESOLVED) {
return sourceResolution;
}
return StringFunctionUtils.resolveStringInputType(right().dataType(), functionName());
}
@Override
protected ProcessorDefinition makeProcessorDefinition() {
return new ConcatFunctionProcessorDefinition(location(), this,
ProcessorDefinitions.toProcessorDefinition(left()),
ProcessorDefinitions.toProcessorDefinition(right()));
}
@Override
public boolean foldable() {
return left().foldable()
&& right().foldable();
}
@Override
public Object fold() {
return doProcessInScripts(left().fold(), right().fold());
}
@Override
protected Concat replaceChildren(Expression newLeft, Expression newRight) {
return new Concat(location(), newLeft, newRight);
}
@Override
protected NodeInfo<? extends Expression> info() {
return NodeInfo.create(this, Concat::new, left(), right());
}
@Override
public ScriptTemplate asScript() {
ScriptTemplate sourceScript1 = asScript(left());
ScriptTemplate sourceScript2 = asScript(right());
return asScriptFrom(sourceScript1, sourceScript2);
}
@Override
protected ScriptTemplate asScriptFrom(ScriptTemplate leftScript, ScriptTemplate rightScript) {
// basically, transform the script to InternalSqlScriptUtils.[function_name](function_or_field1, function_or_field2)
return new ScriptTemplate(format(Locale.ROOT, formatTemplate("{sql}.%s(%s,%s)"),
"concat",
leftScript.template(),
rightScript.template()),
paramsBuilder()
.script(leftScript.params()).script(rightScript.params())
.build(), dataType());
}
@Override
protected ScriptTemplate asScriptFrom(FieldAttribute field) {
return new ScriptTemplate(formatScript("doc[{}].value"),
paramsBuilder().variable(field.isInexact() ? field.exactAttribute().name() : field.name()).build(),
dataType());
}
@Override
public DataType dataType() {
return DataType.KEYWORD;
}
}

View File

@ -0,0 +1,82 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.expression.function.scalar.string;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.xpack.sql.SqlIllegalArgumentException;
import org.elasticsearch.xpack.sql.expression.function.scalar.processor.runtime.BinaryProcessor;
import org.elasticsearch.xpack.sql.expression.function.scalar.processor.runtime.Processor;
import java.io.IOException;
import java.util.Objects;
public class ConcatFunctionProcessor extends BinaryProcessor {
public static final String NAME = "cb";
public ConcatFunctionProcessor(Processor source1, Processor source2) {
super(source1, source2);
}
public ConcatFunctionProcessor(StreamInput in) throws IOException {
super(in);
}
@Override
protected Object doProcess(Object source1, Object source2) {
return doProcessInScripts(source1, source2);
}
/**
* Used in Painless scripting
*/
public static Object doProcessInScripts(Object source1, Object source2) {
if (source1 == null) {
return source2;
}
if (source2 == null) {
return source1;
}
if (!(source1 instanceof String || source1 instanceof Character)) {
throw new SqlIllegalArgumentException("A string/char is required; received [{}]", source1);
}
if (!(source2 instanceof String || source2 instanceof Character)) {
throw new SqlIllegalArgumentException("A string/char is required; received [{}]", source2);
}
return source1.toString().concat(source2.toString());
}
@Override
public String getWriteableName() {
return NAME;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
ConcatFunctionProcessor other = (ConcatFunctionProcessor) obj;
return Objects.equals(left(), other.left())
&& Objects.equals(right(), other.right());
}
@Override
public int hashCode() {
return Objects.hash(left(), right());
}
@Override
protected void doWrite(StreamOutput out) throws IOException {
}
}

View File

@ -0,0 +1,57 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.expression.function.scalar.string;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.function.scalar.processor.definition.BinaryProcessorDefinition;
import org.elasticsearch.xpack.sql.expression.function.scalar.processor.definition.ProcessorDefinition;
import org.elasticsearch.xpack.sql.tree.Location;
import org.elasticsearch.xpack.sql.tree.NodeInfo;
import java.util.Objects;
public class ConcatFunctionProcessorDefinition extends BinaryProcessorDefinition {
public ConcatFunctionProcessorDefinition(Location location, Expression expression, ProcessorDefinition left,
ProcessorDefinition right) {
super(location, expression, left, right);
}
@Override
protected NodeInfo<ConcatFunctionProcessorDefinition> info() {
return NodeInfo.create(this, ConcatFunctionProcessorDefinition::new, expression(), left(), right());
}
@Override
protected BinaryProcessorDefinition replaceChildren(ProcessorDefinition left, ProcessorDefinition right) {
return new ConcatFunctionProcessorDefinition(location(), expression(), left, right);
}
@Override
public ConcatFunctionProcessor asProcessor() {
return new ConcatFunctionProcessor(left().asProcessor(), right().asProcessor());
}
@Override
public int hashCode() {
return Objects.hash(left(), right());
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
ConcatFunctionProcessorDefinition other = (ConcatFunctionProcessorDefinition) obj;
return Objects.equals(left(), other.left())
&& Objects.equals(right(), other.right());
}
}

View File

@ -0,0 +1,139 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.expression.function.scalar.string;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.FieldAttribute;
import org.elasticsearch.xpack.sql.expression.function.scalar.ScalarFunction;
import org.elasticsearch.xpack.sql.expression.function.scalar.processor.definition.ProcessorDefinition;
import org.elasticsearch.xpack.sql.expression.function.scalar.processor.definition.ProcessorDefinitions;
import org.elasticsearch.xpack.sql.expression.function.scalar.script.ScriptTemplate;
import org.elasticsearch.xpack.sql.tree.Location;
import org.elasticsearch.xpack.sql.tree.NodeInfo;
import org.elasticsearch.xpack.sql.type.DataType;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import static java.lang.String.format;
import static org.elasticsearch.xpack.sql.expression.function.scalar.script.ParamsBuilder.paramsBuilder;
import static org.elasticsearch.xpack.sql.expression.function.scalar.script.ScriptTemplate.formatTemplate;
import static org.elasticsearch.xpack.sql.expression.function.scalar.string.InsertFunctionProcessor.doProcess;
/**
* Returns a character string where length characters have been deleted from the source string, beginning at start,
* and where the replacement string has been inserted into the source string, beginning at start.
*/
public class Insert extends ScalarFunction {
private final Expression source, start, length, replacement;
public Insert(Location location, Expression source, Expression start, Expression length, Expression replacement) {
super(location, Arrays.asList(source, start, length, replacement));
this.source = source;
this.start = start;
this.length = length;
this.replacement = replacement;
}
protected TypeResolution resolveType() {
if (!childrenResolved()) {
return new TypeResolution("Unresolved children");
}
TypeResolution sourceResolution = StringFunctionUtils.resolveStringInputType(source.dataType(), functionName());
if (sourceResolution != TypeResolution.TYPE_RESOLVED) {
return sourceResolution;
}
TypeResolution startResolution = StringFunctionUtils.resolveNumericInputType(start.dataType(), functionName());
if (startResolution != TypeResolution.TYPE_RESOLVED) {
return startResolution;
}
TypeResolution lengthResolution = StringFunctionUtils.resolveNumericInputType(length.dataType(), functionName());
if (lengthResolution != TypeResolution.TYPE_RESOLVED) {
return lengthResolution;
}
return StringFunctionUtils.resolveStringInputType(replacement.dataType(), functionName());
}
@Override
public boolean foldable() {
return source.foldable()
&& start.foldable()
&& length.foldable()
&& replacement.foldable();
}
@Override
public Object fold() {
return doProcess(source.fold(), start.fold(), length.fold(), replacement.fold());
}
@Override
protected ProcessorDefinition makeProcessorDefinition() {
return new InsertFunctionProcessorDefinition(location(), this,
ProcessorDefinitions.toProcessorDefinition(source),
ProcessorDefinitions.toProcessorDefinition(start),
ProcessorDefinitions.toProcessorDefinition(length),
ProcessorDefinitions.toProcessorDefinition(replacement));
}
@Override
protected NodeInfo<? extends Expression> info() {
return NodeInfo.create(this, Insert::new, source, start, length, replacement);
}
@Override
public ScriptTemplate asScript() {
ScriptTemplate sourceScript = asScript(source);
ScriptTemplate startScript = asScript(start);
ScriptTemplate lengthScript = asScript(length);
ScriptTemplate replacementScript = asScript(replacement);
return asScriptFrom(sourceScript, startScript, lengthScript, replacementScript);
}
protected ScriptTemplate asScriptFrom(ScriptTemplate sourceScript, ScriptTemplate startScript,
ScriptTemplate lengthScript, ScriptTemplate replacementScript)
{
// basically, transform the script to InternalSqlScriptUtils.[function_name](function_or_field1, function_or_field2,...)
return new ScriptTemplate(format(Locale.ROOT, formatTemplate("{sql}.%s(%s,%s,%s,%s)"),
"insert",
sourceScript.template(),
startScript.template(),
lengthScript.template(),
replacementScript.template()),
paramsBuilder()
.script(sourceScript.params()).script(startScript.params())
.script(lengthScript.params()).script(replacementScript.params())
.build(), dataType());
}
@Override
protected ScriptTemplate asScriptFrom(FieldAttribute field) {
return new ScriptTemplate(formatScript("doc[{}].value"),
paramsBuilder().variable(field.isInexact() ? field.exactAttribute().name() : field.name()).build(),
dataType());
}
@Override
public DataType dataType() {
return DataType.KEYWORD;
}
@Override
public Expression replaceChildren(List<Expression> newChildren) {
if (newChildren.size() != 4) {
throw new IllegalArgumentException("expected [4] children but received [" + newChildren.size() + "]");
}
return new Insert(location(), newChildren.get(0), newChildren.get(1), newChildren.get(2), newChildren.get(3));
}
}

View File

@ -0,0 +1,131 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.expression.function.scalar.string;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.xpack.sql.SqlIllegalArgumentException;
import org.elasticsearch.xpack.sql.expression.function.scalar.processor.runtime.Processor;
import java.io.IOException;
import java.util.Objects;
public class InsertFunctionProcessor implements Processor {
private final Processor source, start, length, replacement;
public static final String NAME = "ins";
public InsertFunctionProcessor(Processor source, Processor start, Processor length, Processor replacement) {
this.source = source;
this.start = start;
this.length = length;
this.replacement = replacement;
}
public InsertFunctionProcessor(StreamInput in) throws IOException {
source = in.readNamedWriteable(Processor.class);
start = in.readNamedWriteable(Processor.class);
length = in.readNamedWriteable(Processor.class);
replacement = in.readNamedWriteable(Processor.class);
}
@Override
public final void writeTo(StreamOutput out) throws IOException {
out.writeNamedWriteable(source());
out.writeNamedWriteable(start());
out.writeNamedWriteable(length());
out.writeNamedWriteable(replacement());
}
@Override
public Object process(Object input) {
return doProcess(source().process(input), start().process(input), length().process(input), replacement().process(input));
}
public static Object doProcess(Object source, Object start, Object length, Object replacement) {
if (source == null) {
return null;
}
if (!(source instanceof String || source instanceof Character)) {
throw new SqlIllegalArgumentException("A string/char is required; received [{}]", source);
}
if (replacement == null) {
return source;
}
if (!(replacement instanceof String || replacement instanceof Character)) {
throw new SqlIllegalArgumentException("A string/char is required; received [{}]", replacement);
}
if (start == null || length == null) {
return source;
}
if (!(start instanceof Number)) {
throw new SqlIllegalArgumentException("A number is required; received [{}]", start);
}
if (!(length instanceof Number)) {
throw new SqlIllegalArgumentException("A number is required; received [{}]", length);
}
if (((Number) length).intValue() < 0) {
throw new SqlIllegalArgumentException("A positive number is required for [length]; received [{}]", length);
}
int startInt = ((Number) start).intValue() - 1;
int realStart = startInt < 0 ? 0 : startInt;
if (startInt > source.toString().length()) {
return source;
}
StringBuilder sb = new StringBuilder(source.toString());
String replString = (replacement.toString());
return sb.replace(realStart,
realStart + ((Number) length).intValue(),
replString).toString();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
InsertFunctionProcessor other = (InsertFunctionProcessor) obj;
return Objects.equals(source(), other.source())
&& Objects.equals(start(), other.start())
&& Objects.equals(length(), other.length())
&& Objects.equals(replacement(), other.replacement());
}
@Override
public int hashCode() {
return Objects.hash(source(), start(), length(), replacement());
}
public Processor source() {
return source;
}
public Processor start() {
return start;
}
public Processor length() {
return length;
}
public Processor replacement() {
return replacement;
}
@Override
public String getWriteableName() {
return NAME;
}
}

View File

@ -0,0 +1,130 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.expression.function.scalar.string;
import org.elasticsearch.xpack.sql.execution.search.SqlSourceBuilder;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.function.scalar.processor.definition.ProcessorDefinition;
import org.elasticsearch.xpack.sql.tree.Location;
import org.elasticsearch.xpack.sql.tree.NodeInfo;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
public class InsertFunctionProcessorDefinition extends ProcessorDefinition {
private final ProcessorDefinition source, start, length, replacement;
public InsertFunctionProcessorDefinition(Location location, Expression expression,
ProcessorDefinition source, ProcessorDefinition start,
ProcessorDefinition length, ProcessorDefinition replacement) {
super(location, expression, Arrays.asList(source, start, length, replacement));
this.source = source;
this.start = start;
this.length = length;
this.replacement = replacement;
}
@Override
public final ProcessorDefinition replaceChildren(List<ProcessorDefinition> newChildren) {
if (newChildren.size() != 4) {
throw new IllegalArgumentException("expected [4] children but received [" + newChildren.size() + "]");
}
return replaceChildren(newChildren.get(0), newChildren.get(1), newChildren.get(2), newChildren.get(3));
}
@Override
public final ProcessorDefinition resolveAttributes(AttributeResolver resolver) {
ProcessorDefinition newSource = source.resolveAttributes(resolver);
ProcessorDefinition newStart = start.resolveAttributes(resolver);
ProcessorDefinition newLength = length.resolveAttributes(resolver);
ProcessorDefinition newReplacement = replacement.resolveAttributes(resolver);
if (newSource == source
&& newStart == start
&& newLength == length
&& newReplacement == replacement) {
return this;
}
return replaceChildren(newSource, newStart, newLength, newReplacement);
}
@Override
public boolean supportedByAggsOnlyQuery() {
return source.supportedByAggsOnlyQuery()
&& start.supportedByAggsOnlyQuery()
&& length.supportedByAggsOnlyQuery()
&& replacement.supportedByAggsOnlyQuery();
}
@Override
public boolean resolved() {
return source.resolved() && start.resolved() && length.resolved() && replacement.resolved();
}
protected ProcessorDefinition replaceChildren(ProcessorDefinition newSource,
ProcessorDefinition newStart,
ProcessorDefinition newLength,
ProcessorDefinition newReplacement) {
return new InsertFunctionProcessorDefinition(location(), expression(), newSource, newStart, newLength, newReplacement);
}
@Override
public final void collectFields(SqlSourceBuilder sourceBuilder) {
source.collectFields(sourceBuilder);
start.collectFields(sourceBuilder);
length.collectFields(sourceBuilder);
replacement.collectFields(sourceBuilder);
}
@Override
protected NodeInfo<InsertFunctionProcessorDefinition> info() {
return NodeInfo.create(this, InsertFunctionProcessorDefinition::new, expression(), source, start, length, replacement);
}
@Override
public InsertFunctionProcessor asProcessor() {
return new InsertFunctionProcessor(source.asProcessor(), start.asProcessor(), length.asProcessor(), replacement.asProcessor());
}
public ProcessorDefinition source() {
return source;
}
public ProcessorDefinition start() {
return start;
}
public ProcessorDefinition length() {
return length;
}
public ProcessorDefinition replacement() {
return replacement;
}
@Override
public int hashCode() {
return Objects.hash(source, start, length, replacement);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
InsertFunctionProcessorDefinition other = (InsertFunctionProcessorDefinition) obj;
return Objects.equals(source, other.source)
&& Objects.equals(start, other.start)
&& Objects.equals(length, other.length)
&& Objects.equals(replacement, other.replacement);
}
}

View File

@ -0,0 +1,49 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.expression.function.scalar.string;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.function.scalar.processor.definition.ProcessorDefinition;
import org.elasticsearch.xpack.sql.expression.function.scalar.processor.definition.ProcessorDefinitions;
import org.elasticsearch.xpack.sql.expression.function.scalar.string.BinaryStringNumericProcessor.BinaryStringNumericOperation;
import org.elasticsearch.xpack.sql.tree.Location;
import org.elasticsearch.xpack.sql.tree.NodeInfo;
import java.util.function.BiFunction;
/**
* Returns the leftmost count characters of a string.
*/
public class Left extends BinaryStringNumericFunction {
public Left(Location location, Expression left, Expression right) {
super(location, left, right);
}
@Override
protected BiFunction<String, Number, String> operation() {
return BinaryStringNumericOperation.LEFT;
}
@Override
protected Left replaceChildren(Expression newLeft, Expression newRight) {
return new Left(location(), newLeft, newRight);
}
@Override
protected ProcessorDefinition makeProcessorDefinition() {
return new BinaryStringNumericProcessorDefinition(location(), this,
ProcessorDefinitions.toProcessorDefinition(left()),
ProcessorDefinitions.toProcessorDefinition(right()),
BinaryStringNumericOperation.LEFT);
}
@Override
protected NodeInfo<Left> info() {
return NodeInfo.create(this, Left::new, left(), right());
}
}

View File

@ -0,0 +1,151 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.expression.function.scalar.string;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.FieldAttribute;
import org.elasticsearch.xpack.sql.expression.function.scalar.ScalarFunction;
import org.elasticsearch.xpack.sql.expression.function.scalar.processor.definition.ProcessorDefinition;
import org.elasticsearch.xpack.sql.expression.function.scalar.processor.definition.ProcessorDefinitions;
import org.elasticsearch.xpack.sql.expression.function.scalar.script.ScriptTemplate;
import org.elasticsearch.xpack.sql.tree.Location;
import org.elasticsearch.xpack.sql.tree.NodeInfo;
import org.elasticsearch.xpack.sql.type.DataType;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import static java.lang.String.format;
import static org.elasticsearch.xpack.sql.expression.function.scalar.script.ParamsBuilder.paramsBuilder;
import static org.elasticsearch.xpack.sql.expression.function.scalar.script.ScriptTemplate.formatTemplate;
import static org.elasticsearch.xpack.sql.expression.function.scalar.string.LocateFunctionProcessor.doProcess;
/**
* Returns the starting position of the first occurrence of the pattern within the source string.
* The search for the first occurrence of the pattern begins with the first character position in the source string
* unless the optional argument, start, is specified. If start is specified, the search begins with the character
* position indicated by the value of start. The first character position in the source string is indicated by the value 1.
* If the pattern is not found within the source string, the value 0 is returned.
*/
public class Locate extends ScalarFunction {
private final Expression pattern, source, start;
public Locate(Location location, Expression pattern, Expression source, Expression start) {
super(location, start != null ? Arrays.asList(pattern, source, start) : Arrays.asList(pattern, source));
this.pattern = pattern;
this.source = source;
this.start = start;
}
protected TypeResolution resolveType() {
if (!childrenResolved()) {
return new TypeResolution("Unresolved children");
}
TypeResolution patternResolution = StringFunctionUtils.resolveStringInputType(pattern.dataType(), functionName());
if (patternResolution != TypeResolution.TYPE_RESOLVED) {
return patternResolution;
}
TypeResolution sourceResolution = StringFunctionUtils.resolveStringInputType(source.dataType(), functionName());
if (sourceResolution != TypeResolution.TYPE_RESOLVED) {
return sourceResolution;
}
return start == null ? TypeResolution.TYPE_RESOLVED : StringFunctionUtils.resolveNumericInputType(start.dataType(), functionName());
}
@Override
protected ProcessorDefinition makeProcessorDefinition() {
LocateFunctionProcessorDefinition processorDefinition;
if (start == null) {
processorDefinition = new LocateFunctionProcessorDefinition(location(), this,
ProcessorDefinitions.toProcessorDefinition(pattern),
ProcessorDefinitions.toProcessorDefinition(source));
}
else {
processorDefinition = new LocateFunctionProcessorDefinition(location(), this,
ProcessorDefinitions.toProcessorDefinition(pattern),
ProcessorDefinitions.toProcessorDefinition(source),
ProcessorDefinitions.toProcessorDefinition(start));
}
return processorDefinition;
}
@Override
protected NodeInfo<? extends Expression> info() {
return NodeInfo.create(this, Locate::new, pattern, source, start);
}
@Override
public boolean foldable() {
return pattern.foldable()
&& source.foldable()
&& (start == null? true : start.foldable());
}
@Override
public Object fold() {
return doProcess(pattern.fold(), source.fold(), (start == null ? null : start.fold()));
}
@Override
public ScriptTemplate asScript() {
ScriptTemplate patternScript = asScript(pattern);
ScriptTemplate sourceScript = asScript(source);
ScriptTemplate startScript = start == null ? null : asScript(start);
return asScriptFrom(patternScript, sourceScript, startScript);
}
protected ScriptTemplate asScriptFrom(ScriptTemplate patternScript, ScriptTemplate sourceScript,
ScriptTemplate startScript)
{
if (start == null) {
return new ScriptTemplate(format(Locale.ROOT, formatTemplate("{sql}.%s(%s,%s)"),
"locate",
patternScript.template(),
sourceScript.template()),
paramsBuilder()
.script(patternScript.params()).script(sourceScript.params())
.build(), dataType());
}
// basically, transform the script to InternalSqlScriptUtils.[function_name](function_or_field1, function_or_field2,...)
return new ScriptTemplate(format(Locale.ROOT, formatTemplate("{sql}.%s(%s,%s,%s)"),
"locate",
patternScript.template(),
sourceScript.template(),
startScript.template()),
paramsBuilder()
.script(patternScript.params()).script(sourceScript.params())
.script(startScript.params())
.build(), dataType());
}
@Override
protected ScriptTemplate asScriptFrom(FieldAttribute field) {
return new ScriptTemplate(formatScript("doc[{}].value"),
paramsBuilder().variable(field.isInexact() ? field.exactAttribute().name() : field.name()).build(),
dataType());
}
@Override
public DataType dataType() {
return DataType.INTEGER;
}
@Override
public Expression replaceChildren(List<Expression> newChildren) {
if (newChildren.size() != 3) {
throw new IllegalArgumentException("expected [3] children but received [" + newChildren.size() + "]");
}
return new Locate(location(), newChildren.get(0), newChildren.get(1), newChildren.get(2));
}
}

View File

@ -0,0 +1,108 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.expression.function.scalar.string;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.xpack.sql.SqlIllegalArgumentException;
import org.elasticsearch.xpack.sql.expression.function.scalar.processor.runtime.Processor;
import java.io.IOException;
import java.util.Objects;
public class LocateFunctionProcessor implements Processor {
private final Processor pattern, source, start;
public static final String NAME = "lc";
public LocateFunctionProcessor(Processor pattern, Processor source, Processor start) {
this.pattern = pattern;
this.source = source;
this.start = start;
}
public LocateFunctionProcessor(StreamInput in) throws IOException {
pattern = in.readNamedWriteable(Processor.class);
source = in.readNamedWriteable(Processor.class);
start = in.readOptionalNamedWriteable(Processor.class);
}
@Override
public final void writeTo(StreamOutput out) throws IOException {
out.writeNamedWriteable(pattern);
out.writeNamedWriteable(source);
out.writeOptionalNamedWriteable(start);
}
@Override
public Object process(Object input) {
return doProcess(pattern().process(input), source().process(input), start() == null ? null : start().process(input));
}
public static Object doProcess(Object pattern, Object source, Object start) {
if (source == null) {
return null;
}
if (!(source instanceof String || source instanceof Character)) {
throw new SqlIllegalArgumentException("A string/char is required; received [{}]", source);
}
if (pattern == null) {
return 0;
}
if (!(pattern instanceof String || pattern instanceof Character)) {
throw new SqlIllegalArgumentException("A string/char is required; received [{}]", pattern);
}
if (start != null && !(start instanceof Number)) {
throw new SqlIllegalArgumentException("A number is required; received [{}]", start);
}
String stringSource = source instanceof Character ? source.toString() : (String) source;
String stringPattern = pattern instanceof Character ? pattern.toString() : (String) pattern;
return (Integer) (1 + (start != null ?
stringSource.indexOf(stringPattern, ((Number) start).intValue() - 1)
: stringSource.indexOf(stringPattern)));
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
LocateFunctionProcessor other = (LocateFunctionProcessor) obj;
return Objects.equals(pattern(), other.pattern())
&& Objects.equals(source(), other.source())
&& Objects.equals(start(), other.start());
}
@Override
public int hashCode() {
return Objects.hash(pattern(), source(), start());
}
public Processor pattern() {
return pattern;
}
public Processor source() {
return source;
}
public Processor start() {
return start;
}
@Override
public String getWriteableName() {
return NAME;
}
}

View File

@ -0,0 +1,126 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.expression.function.scalar.string;
import org.elasticsearch.xpack.sql.execution.search.SqlSourceBuilder;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.function.scalar.processor.definition.ProcessorDefinition;
import org.elasticsearch.xpack.sql.tree.Location;
import org.elasticsearch.xpack.sql.tree.NodeInfo;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
public class LocateFunctionProcessorDefinition extends ProcessorDefinition {
private final ProcessorDefinition pattern, source, start;
public LocateFunctionProcessorDefinition(Location location, Expression expression, ProcessorDefinition pattern,
ProcessorDefinition source, ProcessorDefinition start) {
super(location, expression, Arrays.asList(pattern, source, start));
this.pattern = pattern;
this.source = source;
this.start = start;
}
public LocateFunctionProcessorDefinition(Location location, Expression expression, ProcessorDefinition pattern,
ProcessorDefinition source) {
super(location, expression, Arrays.asList(pattern, source));
this.pattern = pattern;
this.source = source;
this.start = null;
}
@Override
public final ProcessorDefinition replaceChildren(List<ProcessorDefinition> newChildren) {
int childrenSize = newChildren.size();
if (childrenSize > 3 || childrenSize < 2) {
throw new IllegalArgumentException("expected [2 or 3] children but received [" + newChildren.size() + "]");
}
return replaceChildren(newChildren.get(0), newChildren.get(1), childrenSize == 2 ? null : newChildren.get(2));
}
@Override
public final ProcessorDefinition resolveAttributes(AttributeResolver resolver) {
ProcessorDefinition newPattern = pattern.resolveAttributes(resolver);
ProcessorDefinition newSource = source.resolveAttributes(resolver);
ProcessorDefinition newStart = start == null ? start : start.resolveAttributes(resolver);
if (newPattern == pattern && newSource == source && newStart == start) {
return this;
}
return replaceChildren(newPattern, newSource, newStart);
}
@Override
public boolean supportedByAggsOnlyQuery() {
return pattern.supportedByAggsOnlyQuery() && source.supportedByAggsOnlyQuery()
&& (start == null || start.supportedByAggsOnlyQuery());
}
@Override
public boolean resolved() {
return pattern.resolved() && source.resolved() && (start == null || start.resolved());
}
protected ProcessorDefinition replaceChildren(ProcessorDefinition newPattern, ProcessorDefinition newSource,
ProcessorDefinition newStart) {
if (newStart == null) {
return new LocateFunctionProcessorDefinition(location(), expression(), newPattern, newSource);
}
return new LocateFunctionProcessorDefinition(location(), expression(), newPattern, newSource, newStart);
}
@Override
public final void collectFields(SqlSourceBuilder sourceBuilder) {
pattern.collectFields(sourceBuilder);
source.collectFields(sourceBuilder);
if (start != null) {
start.collectFields(sourceBuilder);
}
}
@Override
protected NodeInfo<LocateFunctionProcessorDefinition> info() {
return NodeInfo.create(this, LocateFunctionProcessorDefinition::new, expression(), pattern, source, start);
}
@Override
public LocateFunctionProcessor asProcessor() {
return new LocateFunctionProcessor(pattern.asProcessor(), source.asProcessor(), start == null ? null : start.asProcessor());
}
public ProcessorDefinition source() {
return source;
}
public ProcessorDefinition start() {
return start;
}
public ProcessorDefinition pattern() {
return pattern;
}
@Override
public int hashCode() {
return Objects.hash(pattern, source, start);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
LocateFunctionProcessorDefinition other = (LocateFunctionProcessorDefinition) obj;
return Objects.equals(pattern, other.pattern) && Objects.equals(source, other.source) && Objects.equals(start, other.start);
}
}

View File

@ -0,0 +1,49 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.expression.function.scalar.string;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.function.scalar.processor.definition.ProcessorDefinition;
import org.elasticsearch.xpack.sql.expression.function.scalar.processor.definition.ProcessorDefinitions;
import org.elasticsearch.xpack.sql.expression.function.scalar.string.BinaryStringStringProcessor.BinaryStringStringOperation;
import org.elasticsearch.xpack.sql.tree.Location;
import org.elasticsearch.xpack.sql.tree.NodeInfo;
import java.util.function.BiFunction;
/**
* Returns the position of the first character expression in the second character expression, if not found it returns 0.
*/
public class Position extends BinaryStringStringFunction {
public Position(Location location, Expression left, Expression right) {
super(location, left, right);
}
@Override
protected BiFunction<String, String, Number> operation() {
return BinaryStringStringOperation.POSITION;
}
@Override
protected Position replaceChildren(Expression newLeft, Expression newRight) {
return new Position(location(), newLeft, newRight);
}
@Override
protected ProcessorDefinition makeProcessorDefinition() {
return new BinaryStringStringProcessorDefinition(location(), this,
ProcessorDefinitions.toProcessorDefinition(left()),
ProcessorDefinitions.toProcessorDefinition(right()),
BinaryStringStringOperation.POSITION);
}
@Override
protected NodeInfo<Position> info() {
return NodeInfo.create(this, Position::new, left(), right());
}
}

View File

@ -0,0 +1,49 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.expression.function.scalar.string;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.function.scalar.processor.definition.ProcessorDefinition;
import org.elasticsearch.xpack.sql.expression.function.scalar.processor.definition.ProcessorDefinitions;
import org.elasticsearch.xpack.sql.expression.function.scalar.string.BinaryStringNumericProcessor.BinaryStringNumericOperation;
import org.elasticsearch.xpack.sql.tree.Location;
import org.elasticsearch.xpack.sql.tree.NodeInfo;
import java.util.function.BiFunction;
/**
* Creates a string composed of a string repeated count times.
*/
public class Repeat extends BinaryStringNumericFunction {
public Repeat(Location location, Expression left, Expression right) {
super(location, left, right);
}
@Override
protected BiFunction<String, Number, String> operation() {
return BinaryStringNumericOperation.REPEAT;
}
@Override
protected Repeat replaceChildren(Expression newLeft, Expression newRight) {
return new Repeat(location(), newLeft, newRight);
}
@Override
protected ProcessorDefinition makeProcessorDefinition() {
return new BinaryStringNumericProcessorDefinition(location(), this,
ProcessorDefinitions.toProcessorDefinition(left()),
ProcessorDefinitions.toProcessorDefinition(right()),
BinaryStringNumericOperation.REPEAT);
}
@Override
protected NodeInfo<Repeat> info() {
return NodeInfo.create(this, Repeat::new, left(), right());
}
}

View File

@ -0,0 +1,128 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.expression.function.scalar.string;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.FieldAttribute;
import org.elasticsearch.xpack.sql.expression.function.scalar.ScalarFunction;
import org.elasticsearch.xpack.sql.expression.function.scalar.processor.definition.ProcessorDefinition;
import org.elasticsearch.xpack.sql.expression.function.scalar.processor.definition.ProcessorDefinitions;
import org.elasticsearch.xpack.sql.expression.function.scalar.script.ScriptTemplate;
import org.elasticsearch.xpack.sql.tree.Location;
import org.elasticsearch.xpack.sql.tree.NodeInfo;
import org.elasticsearch.xpack.sql.type.DataType;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import static java.lang.String.format;
import static org.elasticsearch.xpack.sql.expression.function.scalar.script.ParamsBuilder.paramsBuilder;
import static org.elasticsearch.xpack.sql.expression.function.scalar.script.ScriptTemplate.formatTemplate;
import static org.elasticsearch.xpack.sql.expression.function.scalar.string.SubstringFunctionProcessor.doProcess;
/**
* Search the source string for occurrences of the pattern, and replace with the replacement string.
*/
public class Replace extends ScalarFunction {
private final Expression source, pattern, replacement;
public Replace(Location location, Expression source, Expression pattern, Expression replacement) {
super(location, Arrays.asList(source, pattern, replacement));
this.source = source;
this.pattern = pattern;
this.replacement = replacement;
}
protected TypeResolution resolveType() {
if (!childrenResolved()) {
return new TypeResolution("Unresolved children");
}
TypeResolution sourceResolution = StringFunctionUtils.resolveStringInputType(source.dataType(), functionName());
if (sourceResolution != TypeResolution.TYPE_RESOLVED) {
return sourceResolution;
}
TypeResolution patternResolution = StringFunctionUtils.resolveStringInputType(pattern.dataType(), functionName());
if (patternResolution != TypeResolution.TYPE_RESOLVED) {
return patternResolution;
}
return StringFunctionUtils.resolveStringInputType(replacement.dataType(), functionName());
}
@Override
protected ProcessorDefinition makeProcessorDefinition() {
return new ReplaceFunctionProcessorDefinition(location(), this,
ProcessorDefinitions.toProcessorDefinition(source),
ProcessorDefinitions.toProcessorDefinition(pattern),
ProcessorDefinitions.toProcessorDefinition(replacement));
}
@Override
protected NodeInfo<? extends Expression> info() {
return NodeInfo.create(this, Replace::new, source, pattern, replacement);
}
@Override
public boolean foldable() {
return source.foldable()
&& pattern.foldable()
&& replacement.foldable();
}
@Override
public Object fold() {
return doProcess(source.fold(), pattern.fold(), replacement.fold());
}
@Override
public ScriptTemplate asScript() {
ScriptTemplate sourceScript = asScript(source);
ScriptTemplate patternScript = asScript(pattern);
ScriptTemplate replacementScript = asScript(replacement);
return asScriptFrom(sourceScript, patternScript, replacementScript);
}
protected ScriptTemplate asScriptFrom(ScriptTemplate sourceScript, ScriptTemplate patternScript,
ScriptTemplate replacementScript)
{
// basically, transform the script to InternalSqlScriptUtils.[function_name](function_or_field1, function_or_field2,...)
return new ScriptTemplate(format(Locale.ROOT, formatTemplate("{sql}.%s(%s,%s,%s)"),
"replace",
sourceScript.template(),
patternScript.template(),
replacementScript.template()),
paramsBuilder()
.script(sourceScript.params()).script(patternScript.params())
.script(replacementScript.params())
.build(), dataType());
}
@Override
protected ScriptTemplate asScriptFrom(FieldAttribute field) {
return new ScriptTemplate(formatScript("doc[{}].value"),
paramsBuilder().variable(field.isInexact() ? field.exactAttribute().name() : field.name()).build(),
dataType());
}
@Override
public DataType dataType() {
return DataType.KEYWORD;
}
@Override
public Expression replaceChildren(List<Expression> newChildren) {
if (newChildren.size() != 3) {
throw new IllegalArgumentException("expected [3] children but received [" + newChildren.size() + "]");
}
return new Replace(location(), newChildren.get(0), newChildren.get(1), newChildren.get(2));
}
}

View File

@ -0,0 +1,105 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.expression.function.scalar.string;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.xpack.sql.SqlIllegalArgumentException;
import org.elasticsearch.xpack.sql.expression.function.scalar.processor.runtime.Processor;
import java.io.IOException;
import java.util.Objects;
public class ReplaceFunctionProcessor implements Processor {
private final Processor source, pattern, replacement;
public static final String NAME = "r";
public ReplaceFunctionProcessor(Processor source, Processor pattern, Processor replacement) {
this.source = source;
this.pattern = pattern;
this.replacement = replacement;
}
public ReplaceFunctionProcessor(StreamInput in) throws IOException {
source = in.readNamedWriteable(Processor.class);
pattern = in.readNamedWriteable(Processor.class);
replacement = in.readNamedWriteable(Processor.class);
}
@Override
public final void writeTo(StreamOutput out) throws IOException {
out.writeNamedWriteable(source);
out.writeNamedWriteable(pattern);
out.writeNamedWriteable(replacement);
}
@Override
public Object process(Object input) {
return doProcess(source().process(input), pattern().process(input), replacement().process(input));
}
public static Object doProcess(Object source, Object pattern, Object replacement) {
if (source == null) {
return null;
}
if (!(source instanceof String || source instanceof Character)) {
throw new SqlIllegalArgumentException("A string/char is required; received [{}]", source);
}
if (pattern == null || replacement == null) {
return source;
}
if (!(pattern instanceof String || pattern instanceof Character)) {
throw new SqlIllegalArgumentException("A string/char is required; received [{}]", pattern);
}
if (!(replacement instanceof String || replacement instanceof Character)) {
throw new SqlIllegalArgumentException("A string/char is required; received [{}]", replacement);
}
return Strings.replace(source instanceof Character ? source.toString() : (String)source,
pattern instanceof Character ? pattern.toString() : (String) pattern,
replacement instanceof Character ? replacement.toString() : (String) replacement);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
ReplaceFunctionProcessor other = (ReplaceFunctionProcessor) obj;
return Objects.equals(source(), other.source())
&& Objects.equals(pattern(), other.pattern())
&& Objects.equals(replacement(), other.replacement());
}
@Override
public int hashCode() {
return Objects.hash(source(), pattern(), replacement());
}
public Processor source() {
return source;
}
public Processor pattern() {
return pattern;
}
public Processor replacement() {
return replacement;
}
@Override
public String getWriteableName() {
return NAME;
}
}

View File

@ -0,0 +1,113 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.expression.function.scalar.string;
import org.elasticsearch.xpack.sql.execution.search.SqlSourceBuilder;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.function.scalar.processor.definition.ProcessorDefinition;
import org.elasticsearch.xpack.sql.tree.Location;
import org.elasticsearch.xpack.sql.tree.NodeInfo;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
public class ReplaceFunctionProcessorDefinition extends ProcessorDefinition {
private final ProcessorDefinition source, pattern, replacement;
public ReplaceFunctionProcessorDefinition(Location location, Expression expression, ProcessorDefinition source,
ProcessorDefinition pattern, ProcessorDefinition replacement) {
super(location, expression, Arrays.asList(source, pattern, replacement));
this.source = source;
this.pattern = pattern;
this.replacement = replacement;
}
@Override
public final ProcessorDefinition replaceChildren(List<ProcessorDefinition> newChildren) {
if (newChildren.size() != 3) {
throw new IllegalArgumentException("expected [3] children but received [" + newChildren.size() + "]");
}
return replaceChildren(newChildren.get(0), newChildren.get(1), newChildren.get(2));
}
@Override
public final ProcessorDefinition resolveAttributes(AttributeResolver resolver) {
ProcessorDefinition newSource = source.resolveAttributes(resolver);
ProcessorDefinition newPattern = pattern.resolveAttributes(resolver);
ProcessorDefinition newReplacement = replacement.resolveAttributes(resolver);
if (newSource == source && newPattern == pattern && newReplacement == replacement) {
return this;
}
return replaceChildren(newSource, newPattern, newReplacement);
}
@Override
public boolean supportedByAggsOnlyQuery() {
return source.supportedByAggsOnlyQuery() && pattern.supportedByAggsOnlyQuery() && replacement.supportedByAggsOnlyQuery();
}
@Override
public boolean resolved() {
return source.resolved() && pattern.resolved() && replacement.resolved();
}
protected ProcessorDefinition replaceChildren(ProcessorDefinition newSource, ProcessorDefinition newPattern,
ProcessorDefinition newReplacement) {
return new ReplaceFunctionProcessorDefinition(location(), expression(), newSource, newPattern, newReplacement);
}
@Override
public final void collectFields(SqlSourceBuilder sourceBuilder) {
source.collectFields(sourceBuilder);
pattern.collectFields(sourceBuilder);
replacement.collectFields(sourceBuilder);
}
@Override
protected NodeInfo<ReplaceFunctionProcessorDefinition> info() {
return NodeInfo.create(this, ReplaceFunctionProcessorDefinition::new, expression(), source, pattern, replacement);
}
@Override
public ReplaceFunctionProcessor asProcessor() {
return new ReplaceFunctionProcessor(source.asProcessor(), pattern.asProcessor(), replacement.asProcessor());
}
public ProcessorDefinition source() {
return source;
}
public ProcessorDefinition pattern() {
return pattern;
}
public ProcessorDefinition replacement() {
return replacement;
}
@Override
public int hashCode() {
return Objects.hash(source, pattern, replacement);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
ReplaceFunctionProcessorDefinition other = (ReplaceFunctionProcessorDefinition) obj;
return Objects.equals(source, other.source)
&& Objects.equals(pattern, other.pattern)
&& Objects.equals(replacement, other.replacement);
}
}

View File

@ -0,0 +1,49 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.expression.function.scalar.string;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.function.scalar.processor.definition.ProcessorDefinition;
import org.elasticsearch.xpack.sql.expression.function.scalar.processor.definition.ProcessorDefinitions;
import org.elasticsearch.xpack.sql.expression.function.scalar.string.BinaryStringNumericProcessor.BinaryStringNumericOperation;
import org.elasticsearch.xpack.sql.tree.Location;
import org.elasticsearch.xpack.sql.tree.NodeInfo;
import java.util.function.BiFunction;
/**
* Returns the rightmost count characters of a string.
*/
public class Right extends BinaryStringNumericFunction {
public Right(Location location, Expression left, Expression right) {
super(location, left, right);
}
@Override
protected BiFunction<String, Number, String> operation() {
return BinaryStringNumericOperation.RIGHT;
}
@Override
protected Right replaceChildren(Expression newLeft, Expression newRight) {
return new Right(location(), newLeft, newRight);
}
@Override
protected ProcessorDefinition makeProcessorDefinition() {
return new BinaryStringNumericProcessorDefinition(location(), this,
ProcessorDefinitions.toProcessorDefinition(left()),
ProcessorDefinitions.toProcessorDefinition(right()),
BinaryStringNumericOperation.RIGHT);
}
@Override
protected NodeInfo<Right> info() {
return NodeInfo.create(this, Right::new, left(), right());
}
}

View File

@ -5,10 +5,35 @@
*/
package org.elasticsearch.xpack.sql.expression.function.scalar.string;
import org.elasticsearch.xpack.sql.expression.Expression.TypeResolution;
import org.elasticsearch.xpack.sql.type.DataType;
abstract class StringFunctionUtils {
/**
* Trims the trailing whitespace characters from the given String. Uses {@link Character#isWhitespace(char)}
* Extract a substring from the given string, using start index and length of the extracted substring.
*
* @param s the original String
* @param start starting position for the substring within the original string. 0-based index position
* @param length length in characters of the substracted substring
* @return the resulting String
*/
static String substring(String s, int start, int length) {
if (!hasLength(s)) {
return s;
}
if (start < 0)
start = 0;
if (start + 1 > s.length() || length < 0)
return "";
return (start + length > s.length()) ? s.substring(start) : s.substring(start, start + length);
}
/**
* Trims the trailing whitespace characters from the given String. Uses {@link java.lang.Character.isWhitespace(char)}
* to determine if a character is whitespace or not.
*
* @param s the original String
@ -48,4 +73,16 @@ abstract class StringFunctionUtils {
private static boolean hasLength(String s) {
return (s != null && s.length() > 0);
}
static TypeResolution resolveStringInputType(DataType inputType, String functionName) {
return inputType.isString() ?
TypeResolution.TYPE_RESOLVED :
new TypeResolution("'%s' requires a string type, received %s", functionName, inputType.esType);
}
static TypeResolution resolveNumericInputType(DataType inputType, String functionName) {
return inputType.isNumeric() ?
TypeResolution.TYPE_RESOLVED :
new TypeResolution("'%s' requires a numeric type, received %s", functionName, inputType.esType);
}
}

View File

@ -0,0 +1,129 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.expression.function.scalar.string;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.FieldAttribute;
import org.elasticsearch.xpack.sql.expression.function.scalar.ScalarFunction;
import org.elasticsearch.xpack.sql.expression.function.scalar.processor.definition.ProcessorDefinition;
import org.elasticsearch.xpack.sql.expression.function.scalar.processor.definition.ProcessorDefinitions;
import org.elasticsearch.xpack.sql.expression.function.scalar.script.ScriptTemplate;
import org.elasticsearch.xpack.sql.tree.Location;
import org.elasticsearch.xpack.sql.tree.NodeInfo;
import org.elasticsearch.xpack.sql.type.DataType;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import static java.lang.String.format;
import static org.elasticsearch.xpack.sql.expression.function.scalar.script.ParamsBuilder.paramsBuilder;
import static org.elasticsearch.xpack.sql.expression.function.scalar.script.ScriptTemplate.formatTemplate;
import static org.elasticsearch.xpack.sql.expression.function.scalar.string.SubstringFunctionProcessor.doProcess;
/**
* Returns a character string that is derived from the source string, beginning at the character position specified by start
* for length characters.
*/
public class Substring extends ScalarFunction {
private final Expression source, start, length;
public Substring(Location location, Expression source, Expression start, Expression length) {
super(location, Arrays.asList(source, start, length));
this.source = source;
this.start = start;
this.length = length;
}
protected TypeResolution resolveType() {
if (!childrenResolved()) {
return new TypeResolution("Unresolved children");
}
TypeResolution sourceResolution = StringFunctionUtils.resolveStringInputType(source.dataType(), functionName());
if (sourceResolution != TypeResolution.TYPE_RESOLVED) {
return sourceResolution;
}
TypeResolution startResolution = StringFunctionUtils.resolveNumericInputType(start.dataType(), functionName());
if (startResolution != TypeResolution.TYPE_RESOLVED) {
return startResolution;
}
return StringFunctionUtils.resolveNumericInputType(length.dataType(), functionName());
}
@Override
protected ProcessorDefinition makeProcessorDefinition() {
return new SubstringFunctionProcessorDefinition(location(), this,
ProcessorDefinitions.toProcessorDefinition(source),
ProcessorDefinitions.toProcessorDefinition(start),
ProcessorDefinitions.toProcessorDefinition(length));
}
@Override
public boolean foldable() {
return source.foldable()
&& start.foldable()
&& length.foldable();
}
@Override
public Object fold() {
return doProcess(source.fold(), start.fold(), length.fold());
}
@Override
protected NodeInfo<? extends Expression> info() {
return NodeInfo.create(this, Substring::new, source, start, length);
}
@Override
public ScriptTemplate asScript() {
ScriptTemplate sourceScript = asScript(source);
ScriptTemplate startScript = asScript(start);
ScriptTemplate lengthScript = asScript(length);
return asScriptFrom(sourceScript, startScript, lengthScript);
}
protected ScriptTemplate asScriptFrom(ScriptTemplate sourceScript, ScriptTemplate startScript,
ScriptTemplate lengthScript)
{
// basically, transform the script to InternalSqlScriptUtils.[function_name](function_or_field1, function_or_field2,...)
return new ScriptTemplate(format(Locale.ROOT, formatTemplate("{sql}.%s(%s,%s,%s)"),
"substring",
sourceScript.template(),
startScript.template(),
lengthScript.template()),
paramsBuilder()
.script(sourceScript.params()).script(startScript.params())
.script(lengthScript.params())
.build(), dataType());
}
@Override
protected ScriptTemplate asScriptFrom(FieldAttribute field) {
return new ScriptTemplate(formatScript("doc[{}].value"),
paramsBuilder().variable(field.isInexact() ? field.exactAttribute().name() : field.name()).build(),
dataType());
}
@Override
public DataType dataType() {
return DataType.KEYWORD;
}
@Override
public Expression replaceChildren(List<Expression> newChildren) {
if (newChildren.size() != 3) {
throw new IllegalArgumentException("expected [3] children but received [" + newChildren.size() + "]");
}
return new Substring(location(), newChildren.get(0), newChildren.get(1), newChildren.get(2));
}
}

View File

@ -0,0 +1,108 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.expression.function.scalar.string;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.xpack.sql.SqlIllegalArgumentException;
import org.elasticsearch.xpack.sql.expression.function.scalar.processor.runtime.Processor;
import java.io.IOException;
import java.util.Objects;
public class SubstringFunctionProcessor implements Processor {
private final Processor source, start, length;
public static final String NAME = "sb";
public SubstringFunctionProcessor(Processor source, Processor start, Processor length) {
this.source = source;
this.start = start;
this.length = length;
}
public SubstringFunctionProcessor(StreamInput in) throws IOException {
source = in.readNamedWriteable(Processor.class);
start = in.readNamedWriteable(Processor.class);
length = in.readNamedWriteable(Processor.class);
}
@Override
public final void writeTo(StreamOutput out) throws IOException {
out.writeNamedWriteable(source);
out.writeNamedWriteable(start);
out.writeNamedWriteable(length);
}
@Override
public Object process(Object input) {
return doProcess(source.process(input), start.process(input), length.process(input));
}
public static Object doProcess(Object source, Object start, Object length) {
if (source == null) {
return null;
}
if (!(source instanceof String || source instanceof Character)) {
throw new SqlIllegalArgumentException("A string/char is required; received [{}]", source);
}
if (start == null || length == null) {
return source;
}
if (!(start instanceof Number)) {
throw new SqlIllegalArgumentException("A number is required; received [{}]", start);
}
if (!(length instanceof Number)) {
throw new SqlIllegalArgumentException("A number is required; received [{}]", length);
}
if (((Number) length).intValue() < 0) {
throw new SqlIllegalArgumentException("A positive number is required for [length]; received [{}]", length);
}
return StringFunctionUtils.substring(source instanceof Character ? source.toString() : (String) source,
((Number) start).intValue() - 1, // SQL is 1-based when it comes to string manipulation
((Number) length).intValue());
}
protected Processor source() {
return source;
}
protected Processor start() {
return start;
}
protected Processor length() {
return length;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
SubstringFunctionProcessor other = (SubstringFunctionProcessor) obj;
return Objects.equals(source(), other.source())
&& Objects.equals(start(), other.start())
&& Objects.equals(length(), other.length());
}
@Override
public int hashCode() {
return Objects.hash(source(), start(), length());
}
@Override
public String getWriteableName() {
return NAME;
}
}

View File

@ -0,0 +1,111 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.expression.function.scalar.string;
import org.elasticsearch.xpack.sql.execution.search.SqlSourceBuilder;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.function.scalar.processor.definition.ProcessorDefinition;
import org.elasticsearch.xpack.sql.tree.Location;
import org.elasticsearch.xpack.sql.tree.NodeInfo;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
public class SubstringFunctionProcessorDefinition extends ProcessorDefinition {
private final ProcessorDefinition source, start, length;
public SubstringFunctionProcessorDefinition(Location location, Expression expression, ProcessorDefinition source,
ProcessorDefinition start, ProcessorDefinition length) {
super(location, expression, Arrays.asList(source, start, length));
this.source = source;
this.start = start;
this.length = length;
}
@Override
public final ProcessorDefinition replaceChildren(List<ProcessorDefinition> newChildren) {
if (newChildren.size() != 3) {
throw new IllegalArgumentException("expected [3] children but received [" + newChildren.size() + "]");
}
return replaceChildren(newChildren.get(0), newChildren.get(1), newChildren.get(2));
}
@Override
public final ProcessorDefinition resolveAttributes(AttributeResolver resolver) {
ProcessorDefinition newSource = source.resolveAttributes(resolver);
ProcessorDefinition newStart = start.resolveAttributes(resolver);
ProcessorDefinition newLength = length.resolveAttributes(resolver);
if (newSource == source && newStart == start && newLength == length) {
return this;
}
return replaceChildren(newSource, newStart, newLength);
}
@Override
public boolean supportedByAggsOnlyQuery() {
return source.supportedByAggsOnlyQuery() && start.supportedByAggsOnlyQuery() && length.supportedByAggsOnlyQuery();
}
@Override
public boolean resolved() {
return source.resolved() && start.resolved() && length.resolved();
}
protected ProcessorDefinition replaceChildren(ProcessorDefinition newSource, ProcessorDefinition newStart,
ProcessorDefinition newLength) {
return new SubstringFunctionProcessorDefinition(location(), expression(), newSource, newStart, newLength);
}
@Override
public final void collectFields(SqlSourceBuilder sourceBuilder) {
source.collectFields(sourceBuilder);
start.collectFields(sourceBuilder);
length.collectFields(sourceBuilder);
}
@Override
protected NodeInfo<SubstringFunctionProcessorDefinition> info() {
return NodeInfo.create(this, SubstringFunctionProcessorDefinition::new, expression(), source, start, length);
}
@Override
public SubstringFunctionProcessor asProcessor() {
return new SubstringFunctionProcessor(source.asProcessor(), start.asProcessor(), length.asProcessor());
}
public ProcessorDefinition source() {
return source;
}
public ProcessorDefinition start() {
return start;
}
public ProcessorDefinition length() {
return length;
}
@Override
public int hashCode() {
return Objects.hash(source, start, length);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
SubstringFunctionProcessorDefinition other = (SubstringFunctionProcessorDefinition) obj;
return Objects.equals(source, other.source) && Objects.equals(start, other.start) && Objects.equals(length, other.length);
}
}

View File

@ -6,7 +6,14 @@
package org.elasticsearch.xpack.sql.expression.function.scalar.whitelist;
import org.elasticsearch.xpack.sql.expression.function.scalar.datetime.DateTimeFunction;
import org.elasticsearch.xpack.sql.expression.function.scalar.string.BinaryStringNumericProcessor.BinaryStringNumericOperation;
import org.elasticsearch.xpack.sql.expression.function.scalar.string.BinaryStringStringProcessor.BinaryStringStringOperation;
import org.elasticsearch.xpack.sql.expression.function.scalar.string.ConcatFunctionProcessor;
import org.elasticsearch.xpack.sql.expression.function.scalar.string.InsertFunctionProcessor;
import org.elasticsearch.xpack.sql.expression.function.scalar.string.LocateFunctionProcessor;
import org.elasticsearch.xpack.sql.expression.function.scalar.string.ReplaceFunctionProcessor;
import org.elasticsearch.xpack.sql.expression.function.scalar.string.StringProcessor.StringOperation;
import org.elasticsearch.xpack.sql.expression.function.scalar.string.SubstringFunctionProcessor;
/**
* Whitelisted class for SQL scripts.
@ -60,4 +67,44 @@ public final class InternalSqlScriptUtils {
public static String space(Number n) {
return (String) StringOperation.SPACE.apply(n);
}
public static String left(String s, int count) {
return BinaryStringNumericOperation.LEFT.apply(s, count);
}
public static String right(String s, int count) {
return BinaryStringNumericOperation.RIGHT.apply(s, count);
}
public static String concat(String s1, String s2) {
return ConcatFunctionProcessor.doProcessInScripts(s1, s2).toString();
}
public static String repeat(String s, int count) {
return BinaryStringNumericOperation.REPEAT.apply(s, count);
}
public static Integer position(String s1, String s2) {
return (Integer) BinaryStringStringOperation.POSITION.apply(s1, s2);
}
public static String insert(String s, int start, int length, String r) {
return InsertFunctionProcessor.doProcess(s, start, length, r).toString();
}
public static String substring(String s, int start, int length) {
return SubstringFunctionProcessor.doProcess(s, start, length).toString();
}
public static String replace(String s1, String s2, String s3) {
return ReplaceFunctionProcessor.doProcess(s1, s2, s3).toString();
}
public static Integer locate(String s1, String s2, Integer pos) {
return (Integer) LocateFunctionProcessor.doProcess(s1, s2, pos);
}
public static Integer locate(String s1, String s2) {
return locate(s1, s2, null);
}
}

View File

@ -19,4 +19,14 @@ class org.elasticsearch.xpack.sql.expression.function.scalar.whitelist.InternalS
String rtrim(String)
String ltrim(String)
String space(Number)
String left(String, int)
String right(String, int)
String concat(String, String)
String repeat(String, int)
Integer position(String, String)
String insert(String, int, int, String)
String substring(String, int, int)
String replace(String, String, String)
Integer locate(String, String)
Integer locate(String, String, Integer)
}

View File

@ -55,7 +55,7 @@ public class VerifierErrorMessagesTests extends ESTestCase {
}
public void testMisspelledFunction() {
assertEquals("1:8: Unknown function [COONT], did you mean any of [COUNT, COT]?", verify("SELECT COONT(bool) FROM test"));
assertEquals("1:8: Unknown function [COONT], did you mean any of [COUNT, COT, CONCAT]?", verify("SELECT COONT(bool) FROM test"));
}
public void testMissingColumnInGroupBy() {

View File

@ -0,0 +1,70 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.expression.function.scalar;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.xpack.sql.expression.Literal;
import java.util.BitSet;
import java.util.Iterator;
import static org.elasticsearch.xpack.sql.tree.Location.EMPTY;
public final class FunctionTestUtils {
public static Literal l(Object value) {
return Literal.of(EMPTY, value);
}
public static Literal randomStringLiteral() {
return l(ESTestCase.randomRealisticUnicodeOfLength(1024));
}
public static Literal randomIntLiteral() {
return l(ESTestCase.randomInt());
}
public static class Combinations implements Iterable<BitSet> {
private int n;
private int k;
public Combinations(int n, int k) {
this.n = n;
this.k = k;
}
@Override
public Iterator<BitSet> iterator() {
return new Iterator<BitSet>() {
BitSet bs = new BitSet(n);
{
bs.set(0, k);
}
@Override
public boolean hasNext() {
return bs != null;
}
@Override
public BitSet next() {
BitSet old = (BitSet) bs.clone();
int b = bs.previousClearBit(n - 1);
int b1 = bs.previousSetBit(b);
if (b1 == -1)
bs = null;
else {
bs.clear(b1);
bs.set(b1 + 1, b1 + (n - b) + 1);
bs.clear(b1 + (n - b) + 1, n);
}
return old;
}
};
}
}
}

View File

@ -0,0 +1,146 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.expression.function.scalar.string;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.function.scalar.processor.definition.BinaryProcessorDefinition;
import org.elasticsearch.xpack.sql.expression.function.scalar.processor.definition.ProcessorDefinition;
import org.elasticsearch.xpack.sql.expression.function.scalar.string.BinaryStringNumericProcessor.BinaryStringNumericOperation;
import org.elasticsearch.xpack.sql.tree.AbstractNodeTestCase;
import org.elasticsearch.xpack.sql.tree.Location;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import static org.elasticsearch.xpack.sql.expression.function.scalar.FunctionTestUtils.randomIntLiteral;
import static org.elasticsearch.xpack.sql.expression.function.scalar.FunctionTestUtils.randomStringLiteral;
import static org.elasticsearch.xpack.sql.expression.function.scalar.processor.definition.ProcessorDefinitions.toProcessorDefinition;
import static org.elasticsearch.xpack.sql.tree.LocationTests.randomLocation;
public class BinaryStringNumericProcessorDefinitionTests
extends AbstractNodeTestCase<BinaryStringNumericProcessorDefinition, ProcessorDefinition> {
@Override
protected BinaryStringNumericProcessorDefinition randomInstance() {
return randomBinaryStringNumericProcessorDefinition();
}
private Expression randomBinaryStringNumericExpression() {
return randomBinaryStringNumericProcessorDefinition().expression();
}
private BinaryStringNumericOperation randomBinaryStringNumericOperation() {
return randomBinaryStringNumericProcessorDefinition().operation();
}
public static BinaryStringNumericProcessorDefinition randomBinaryStringNumericProcessorDefinition() {
List<ProcessorDefinition> functions = new ArrayList<>();
functions.add(new Left(randomLocation(), randomStringLiteral(), randomIntLiteral()).makeProcessorDefinition());
functions.add(new Right(randomLocation(), randomStringLiteral(), randomIntLiteral()).makeProcessorDefinition());
functions.add(new Repeat(randomLocation(), randomStringLiteral(), randomIntLiteral()).makeProcessorDefinition());
return (BinaryStringNumericProcessorDefinition) randomFrom(functions);
}
@Override
public void testTransform() {
// test transforming only the properties (location, expression, operation),
// skipping the children (the two parameters of the binary function) which are tested separately
BinaryStringNumericProcessorDefinition b1 = randomInstance();
Expression newExpression = randomValueOtherThan(b1.expression(), () -> randomBinaryStringNumericExpression());
BinaryStringNumericProcessorDefinition newB = new BinaryStringNumericProcessorDefinition(
b1.location(),
newExpression,
b1.left(),
b1.right(),
b1.operation());
assertEquals(newB, b1.transformPropertiesOnly(v -> Objects.equals(v, b1.expression()) ? newExpression : v, Expression.class));
BinaryStringNumericProcessorDefinition b2 = randomInstance();
BinaryStringNumericOperation newOp = randomValueOtherThan(b2.operation(), () -> randomBinaryStringNumericOperation());
newB = new BinaryStringNumericProcessorDefinition(
b2.location(),
b2.expression(),
b2.left(),
b2.right(),
newOp);
assertEquals(newB,
b2.transformPropertiesOnly(v -> Objects.equals(v, b2.operation()) ? newOp : v, BinaryStringNumericOperation.class));
BinaryStringNumericProcessorDefinition b3 = randomInstance();
Location newLoc = randomValueOtherThan(b3.location(), () -> randomLocation());
newB = new BinaryStringNumericProcessorDefinition(
newLoc,
b3.expression(),
b3.left(),
b3.right(),
b3.operation());
assertEquals(newB,
b3.transformPropertiesOnly(v -> Objects.equals(v, b3.location()) ? newLoc : v, Location.class));
}
@Override
public void testReplaceChildren() {
BinaryStringNumericProcessorDefinition b = randomInstance();
ProcessorDefinition newLeft = toProcessorDefinition((Expression) randomValueOtherThan(b.left(), () -> randomStringLiteral()));
ProcessorDefinition newRight = toProcessorDefinition((Expression) randomValueOtherThan(b.right(), () -> randomIntLiteral()));
BinaryStringNumericProcessorDefinition newB =
new BinaryStringNumericProcessorDefinition(b.location(), b.expression(), b.left(), b.right(), b.operation());
BinaryProcessorDefinition transformed = newB.replaceChildren(newLeft, b.right());
assertEquals(transformed.left(), newLeft);
assertEquals(transformed.location(), b.location());
assertEquals(transformed.expression(), b.expression());
assertEquals(transformed.right(), b.right());
transformed = newB.replaceChildren(b.left(), newRight);
assertEquals(transformed.left(), b.left());
assertEquals(transformed.location(), b.location());
assertEquals(transformed.expression(), b.expression());
assertEquals(transformed.right(), newRight);
transformed = newB.replaceChildren(newLeft, newRight);
assertEquals(transformed.left(), newLeft);
assertEquals(transformed.location(), b.location());
assertEquals(transformed.expression(), b.expression());
assertEquals(transformed.right(), newRight);
}
@Override
protected BinaryStringNumericProcessorDefinition mutate(BinaryStringNumericProcessorDefinition instance) {
List<Function<BinaryStringNumericProcessorDefinition, BinaryStringNumericProcessorDefinition>> randoms = new ArrayList<>();
randoms.add(f -> new BinaryStringNumericProcessorDefinition(f.location(),
f.expression(),
toProcessorDefinition((Expression) randomValueOtherThan(f.left(), () -> randomStringLiteral())),
f.right(),
f.operation()));
randoms.add(f -> new BinaryStringNumericProcessorDefinition(f.location(),
f.expression(),
f.left(),
toProcessorDefinition((Expression) randomValueOtherThan(f.right(), () -> randomIntLiteral())),
f.operation()));
randoms.add(f -> new BinaryStringNumericProcessorDefinition(f.location(),
f.expression(),
toProcessorDefinition((Expression) randomValueOtherThan(f.left(), () -> randomStringLiteral())),
toProcessorDefinition((Expression) randomValueOtherThan(f.right(), () -> randomIntLiteral())),
f.operation()));
return randomFrom(randoms).apply(instance);
}
@Override
protected BinaryStringNumericProcessorDefinition copy(BinaryStringNumericProcessorDefinition instance) {
return new BinaryStringNumericProcessorDefinition(instance.location(),
instance.expression(),
instance.left(),
instance.right(),
instance.operation());
}
}

View File

@ -0,0 +1,112 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.expression.function.scalar.string;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.io.stream.Writeable.Reader;
import org.elasticsearch.test.AbstractWireSerializingTestCase;
import org.elasticsearch.xpack.sql.SqlIllegalArgumentException;
import org.elasticsearch.xpack.sql.expression.function.scalar.Processors;
import org.elasticsearch.xpack.sql.expression.function.scalar.processor.runtime.ConstantProcessor;
import org.elasticsearch.xpack.sql.expression.function.scalar.string.BinaryStringNumericProcessor.BinaryStringNumericOperation;
import static org.elasticsearch.xpack.sql.expression.function.scalar.FunctionTestUtils.l;
import static org.elasticsearch.xpack.sql.tree.Location.EMPTY;
public class BinaryStringNumericProcessorTests extends AbstractWireSerializingTestCase<BinaryStringNumericProcessor> {
@Override
protected BinaryStringNumericProcessor createTestInstance() {
return new BinaryStringNumericProcessor(
new ConstantProcessor(randomRealisticUnicodeOfLengthBetween(1, 128)),
new ConstantProcessor(randomInt(256)),
randomFrom(BinaryStringNumericOperation.values()));
}
@Override
protected Reader<BinaryStringNumericProcessor> instanceReader() {
return BinaryStringNumericProcessor::new;
}
@Override
protected NamedWriteableRegistry getNamedWriteableRegistry() {
return new NamedWriteableRegistry(Processors.getNamedWriteables());
}
public void testLeftFunctionWithValidInput() {
assertEquals("foo", new Left(EMPTY, l("foo bar"), l(3)).makeProcessorDefinition().asProcessor().process(null));
assertEquals("foo bar", new Left(EMPTY, l("foo bar"), l(7)).makeProcessorDefinition().asProcessor().process(null));
assertEquals("foo bar", new Left(EMPTY, l("foo bar"), l(123)).makeProcessorDefinition().asProcessor().process(null));
assertEquals("f", new Left(EMPTY, l('f'), l(1)).makeProcessorDefinition().asProcessor().process(null));
}
public void testLeftFunctionWithEdgeCases() {
assertNull(new Left(EMPTY, l("foo bar"), l(null)).makeProcessorDefinition().asProcessor().process(null));
assertNull(new Left(EMPTY, l(null), l(3)).makeProcessorDefinition().asProcessor().process(null));
assertNull(new Left(EMPTY, l(null), l(null)).makeProcessorDefinition().asProcessor().process(null));
assertEquals("", new Left(EMPTY, l("foo bar"), l(-1)).makeProcessorDefinition().asProcessor().process(null));
assertEquals("", new Left(EMPTY, l("foo bar"), l(0)).makeProcessorDefinition().asProcessor().process(null));
assertEquals("", new Left(EMPTY, l('f'), l(0)).makeProcessorDefinition().asProcessor().process(null));
}
public void testLeftFunctionInputValidation() {
SqlIllegalArgumentException siae = expectThrows(SqlIllegalArgumentException.class,
() -> new Left(EMPTY, l(5), l(3)).makeProcessorDefinition().asProcessor().process(null));
assertEquals("A string/char is required; received [5]", siae.getMessage());
siae = expectThrows(SqlIllegalArgumentException.class,
() -> new Left(EMPTY, l("foo bar"), l("baz")).makeProcessorDefinition().asProcessor().process(null));
assertEquals("A number is required; received [baz]", siae.getMessage());
}
public void testRightFunctionWithValidInput() {
assertEquals("bar", new Right(EMPTY, l("foo bar"), l(3)).makeProcessorDefinition().asProcessor().process(null));
assertEquals("foo bar", new Right(EMPTY, l("foo bar"), l(7)).makeProcessorDefinition().asProcessor().process(null));
assertEquals("foo bar", new Right(EMPTY, l("foo bar"), l(123)).makeProcessorDefinition().asProcessor().process(null));
assertEquals("f", new Right(EMPTY, l('f'), l(1)).makeProcessorDefinition().asProcessor().process(null));
}
public void testRightFunctionWithEdgeCases() {
assertNull(new Right(EMPTY, l("foo bar"), l(null)).makeProcessorDefinition().asProcessor().process(null));
assertNull(new Right(EMPTY, l(null), l(3)).makeProcessorDefinition().asProcessor().process(null));
assertNull(new Right(EMPTY, l(null), l(null)).makeProcessorDefinition().asProcessor().process(null));
assertEquals("", new Right(EMPTY, l("foo bar"), l(-1)).makeProcessorDefinition().asProcessor().process(null));
assertEquals("", new Right(EMPTY, l("foo bar"), l(0)).makeProcessorDefinition().asProcessor().process(null));
assertEquals("", new Right(EMPTY, l('f'), l(0)).makeProcessorDefinition().asProcessor().process(null));
}
public void testRightFunctionInputValidation() {
SqlIllegalArgumentException siae = expectThrows(SqlIllegalArgumentException.class,
() -> new Right(EMPTY, l(5), l(3)).makeProcessorDefinition().asProcessor().process(null));
assertEquals("A string/char is required; received [5]", siae.getMessage());
siae = expectThrows(SqlIllegalArgumentException.class,
() -> new Right(EMPTY, l("foo bar"), l("baz")).makeProcessorDefinition().asProcessor().process(null));
assertEquals("A number is required; received [baz]", siae.getMessage());
}
public void testRepeatFunctionWithValidInput() {
assertEquals("foofoofoo", new Repeat(EMPTY, l("foo"), l(3)).makeProcessorDefinition().asProcessor().process(null));
assertEquals("foo", new Repeat(EMPTY, l("foo"), l(1)).makeProcessorDefinition().asProcessor().process(null));
assertEquals("fff", new Repeat(EMPTY, l('f'), l(3)).makeProcessorDefinition().asProcessor().process(null));
}
public void testRepeatFunctionWithEdgeCases() {
assertNull(new Repeat(EMPTY, l("foo"), l(null)).makeProcessorDefinition().asProcessor().process(null));
assertNull(new Repeat(EMPTY, l(null), l(3)).makeProcessorDefinition().asProcessor().process(null));
assertNull(new Repeat(EMPTY, l(null), l(null)).makeProcessorDefinition().asProcessor().process(null));
assertNull(new Repeat(EMPTY, l("foo"), l(-1)).makeProcessorDefinition().asProcessor().process(null));
assertNull(new Repeat(EMPTY, l("foo"), l(0)).makeProcessorDefinition().asProcessor().process(null));
}
public void testRepeatFunctionInputsValidation() {
SqlIllegalArgumentException siae = expectThrows(SqlIllegalArgumentException.class,
() -> new Repeat(EMPTY, l(5), l(3)).makeProcessorDefinition().asProcessor().process(null));
assertEquals("A string/char is required; received [5]", siae.getMessage());
siae = expectThrows(SqlIllegalArgumentException.class,
() -> new Repeat(EMPTY, l("foo bar"), l("baz")).makeProcessorDefinition().asProcessor().process(null));
assertEquals("A number is required; received [baz]", siae.getMessage());
}
}

View File

@ -0,0 +1,130 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.expression.function.scalar.string;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.function.scalar.processor.definition.BinaryProcessorDefinition;
import org.elasticsearch.xpack.sql.expression.function.scalar.processor.definition.ProcessorDefinition;
import org.elasticsearch.xpack.sql.tree.AbstractNodeTestCase;
import org.elasticsearch.xpack.sql.tree.Location;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import static org.elasticsearch.xpack.sql.expression.function.scalar.FunctionTestUtils.randomStringLiteral;
import static org.elasticsearch.xpack.sql.expression.function.scalar.processor.definition.ProcessorDefinitions.toProcessorDefinition;
import static org.elasticsearch.xpack.sql.tree.LocationTests.randomLocation;
public class BinaryStringStringProcessorDefinitionTests
extends AbstractNodeTestCase<BinaryStringStringProcessorDefinition, ProcessorDefinition> {
@Override
protected BinaryStringStringProcessorDefinition randomInstance() {
return randomBinaryStringStringProcessorDefinition();
}
private Expression randomBinaryStringStringExpression() {
return randomBinaryStringStringProcessorDefinition().expression();
}
public static BinaryStringStringProcessorDefinition randomBinaryStringStringProcessorDefinition() {
List<ProcessorDefinition> functions = new ArrayList<>();
functions.add(new Position(
randomLocation(),
randomStringLiteral(),
randomStringLiteral()
).makeProcessorDefinition());
// if we decide to add DIFFERENCE(string,string) in the future, here we'd add it as well
return (BinaryStringStringProcessorDefinition) randomFrom(functions);
}
@Override
public void testTransform() {
// test transforming only the properties (location, expression),
// skipping the children (the two parameters of the binary function) which are tested separately
BinaryStringStringProcessorDefinition b1 = randomInstance();
Expression newExpression = randomValueOtherThan(b1.expression(), () -> randomBinaryStringStringExpression());
BinaryStringStringProcessorDefinition newB = new BinaryStringStringProcessorDefinition(
b1.location(),
newExpression,
b1.left(),
b1.right(),
b1.operation());
assertEquals(newB, b1.transformPropertiesOnly(v -> Objects.equals(v, b1.expression()) ? newExpression : v, Expression.class));
BinaryStringStringProcessorDefinition b2 = randomInstance();
Location newLoc = randomValueOtherThan(b2.location(), () -> randomLocation());
newB = new BinaryStringStringProcessorDefinition(
newLoc,
b2.expression(),
b2.left(),
b2.right(),
b2.operation());
assertEquals(newB,
b2.transformPropertiesOnly(v -> Objects.equals(v, b2.location()) ? newLoc : v, Location.class));
}
@Override
public void testReplaceChildren() {
BinaryStringStringProcessorDefinition b = randomInstance();
ProcessorDefinition newLeft = toProcessorDefinition((Expression) randomValueOtherThan(b.left(), () -> randomStringLiteral()));
ProcessorDefinition newRight = toProcessorDefinition((Expression) randomValueOtherThan(b.right(), () -> randomStringLiteral()));
BinaryStringStringProcessorDefinition newB =
new BinaryStringStringProcessorDefinition(b.location(), b.expression(), b.left(), b.right(), b.operation());
BinaryProcessorDefinition transformed = newB.replaceChildren(newLeft, b.right());
assertEquals(transformed.left(), newLeft);
assertEquals(transformed.location(), b.location());
assertEquals(transformed.expression(), b.expression());
assertEquals(transformed.right(), b.right());
transformed = newB.replaceChildren(b.left(), newRight);
assertEquals(transformed.left(), b.left());
assertEquals(transformed.location(), b.location());
assertEquals(transformed.expression(), b.expression());
assertEquals(transformed.right(), newRight);
transformed = newB.replaceChildren(newLeft, newRight);
assertEquals(transformed.left(), newLeft);
assertEquals(transformed.location(), b.location());
assertEquals(transformed.expression(), b.expression());
assertEquals(transformed.right(), newRight);
}
@Override
protected BinaryStringStringProcessorDefinition mutate(BinaryStringStringProcessorDefinition instance) {
List<Function<BinaryStringStringProcessorDefinition, BinaryStringStringProcessorDefinition>> randoms = new ArrayList<>();
randoms.add(f -> new BinaryStringStringProcessorDefinition(f.location(),
f.expression(),
toProcessorDefinition((Expression) randomValueOtherThan(f.left(), () -> randomStringLiteral())),
f.right(),
f.operation()));
randoms.add(f -> new BinaryStringStringProcessorDefinition(f.location(),
f.expression(),
f.left(),
toProcessorDefinition((Expression) randomValueOtherThan(f.right(), () -> randomStringLiteral())),
f.operation()));
randoms.add(f -> new BinaryStringStringProcessorDefinition(f.location(),
f.expression(),
toProcessorDefinition((Expression) randomValueOtherThan(f.left(), () -> randomStringLiteral())),
toProcessorDefinition((Expression) randomValueOtherThan(f.right(), () -> randomStringLiteral())),
f.operation()));
return randomFrom(randoms).apply(instance);
}
@Override
protected BinaryStringStringProcessorDefinition copy(BinaryStringStringProcessorDefinition instance) {
return new BinaryStringStringProcessorDefinition(instance.location(),
instance.expression(),
instance.left(),
instance.right(),
instance.operation());
}
}

View File

@ -0,0 +1,63 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.expression.function.scalar.string;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.io.stream.Writeable.Reader;
import org.elasticsearch.test.AbstractWireSerializingTestCase;
import org.elasticsearch.xpack.sql.SqlIllegalArgumentException;
import org.elasticsearch.xpack.sql.expression.function.scalar.Processors;
import org.elasticsearch.xpack.sql.expression.function.scalar.processor.runtime.ConstantProcessor;
import org.elasticsearch.xpack.sql.expression.function.scalar.string.BinaryStringStringProcessor.BinaryStringStringOperation;
import static org.elasticsearch.xpack.sql.tree.Location.EMPTY;
import static org.elasticsearch.xpack.sql.expression.function.scalar.FunctionTestUtils.l;
public class BinaryStringStringProcessorTests extends AbstractWireSerializingTestCase<BinaryStringStringProcessor> {
@Override
protected BinaryStringStringProcessor createTestInstance() {
return new BinaryStringStringProcessor(
new ConstantProcessor(randomRealisticUnicodeOfLengthBetween(1, 128)),
new ConstantProcessor(randomRealisticUnicodeOfLengthBetween(1, 128)),
randomFrom(BinaryStringStringOperation.values()));
}
@Override
protected Reader<BinaryStringStringProcessor> instanceReader() {
return BinaryStringStringProcessor::new;
}
@Override
protected NamedWriteableRegistry getNamedWriteableRegistry() {
return new NamedWriteableRegistry(Processors.getNamedWriteables());
}
public void testPositionFunctionWithValidInput() {
assertEquals(4, new Position(EMPTY, l("bar"), l("foobar")).makeProcessorDefinition().asProcessor().process(null));
assertEquals(1, new Position(EMPTY, l("foo"), l("foobar")).makeProcessorDefinition().asProcessor().process(null));
assertEquals(0, new Position(EMPTY, l("foo"), l("bar")).makeProcessorDefinition().asProcessor().process(null));
assertEquals(3, new Position(EMPTY, l('r'), l("bar")).makeProcessorDefinition().asProcessor().process(null));
assertEquals(0, new Position(EMPTY, l('z'), l("bar")).makeProcessorDefinition().asProcessor().process(null));
assertEquals(1, new Position(EMPTY, l('b'), l('b')).makeProcessorDefinition().asProcessor().process(null));
}
public void testPositionFunctionWithEdgeCases() {
assertNull(new Position(EMPTY, l("foo"), l(null)).makeProcessorDefinition().asProcessor().process(null));
assertNull(new Position(EMPTY, l(null), l("foo")).makeProcessorDefinition().asProcessor().process(null));
assertNull(new Position(EMPTY, l(null), l(null)).makeProcessorDefinition().asProcessor().process(null));
}
public void testPositionFunctionInputsValidation() {
SqlIllegalArgumentException siae = expectThrows(SqlIllegalArgumentException.class,
() -> new Position(EMPTY, l(5), l("foo")).makeProcessorDefinition().asProcessor().process(null));
assertEquals("A string/char is required; received [5]", siae.getMessage());
siae = expectThrows(SqlIllegalArgumentException.class,
() -> new Position(EMPTY, l("foo bar"), l(3)).makeProcessorDefinition().asProcessor().process(null));
assertEquals("A string/char is required; received [3]", siae.getMessage());
}
}

View File

@ -0,0 +1,121 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.expression.function.scalar.string;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.function.scalar.processor.definition.BinaryProcessorDefinition;
import org.elasticsearch.xpack.sql.expression.function.scalar.processor.definition.ProcessorDefinition;
import org.elasticsearch.xpack.sql.tree.AbstractNodeTestCase;
import org.elasticsearch.xpack.sql.tree.Location;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import static org.elasticsearch.xpack.sql.expression.function.scalar.FunctionTestUtils.randomStringLiteral;
import static org.elasticsearch.xpack.sql.expression.function.scalar.processor.definition.ProcessorDefinitions.toProcessorDefinition;
import static org.elasticsearch.xpack.sql.tree.LocationTests.randomLocation;
public class ConcatFunctionProcessorDefinitionTests extends AbstractNodeTestCase<ConcatFunctionProcessorDefinition, ProcessorDefinition> {
@Override
protected ConcatFunctionProcessorDefinition randomInstance() {
return randomConcatFunctionProcessorDefinition();
}
private Expression randomConcatFunctionExpression() {
return randomConcatFunctionProcessorDefinition().expression();
}
public static ConcatFunctionProcessorDefinition randomConcatFunctionProcessorDefinition() {
return (ConcatFunctionProcessorDefinition) new Concat(
randomLocation(),
randomStringLiteral(),
randomStringLiteral())
.makeProcessorDefinition();
}
@Override
public void testTransform() {
// test transforming only the properties (location, expression),
// skipping the children (the two parameters of the binary function) which are tested separately
ConcatFunctionProcessorDefinition b1 = randomInstance();
Expression newExpression = randomValueOtherThan(b1.expression(), () -> randomConcatFunctionExpression());
ConcatFunctionProcessorDefinition newB = new ConcatFunctionProcessorDefinition(
b1.location(),
newExpression,
b1.left(),
b1.right());
assertEquals(newB, b1.transformPropertiesOnly(v -> Objects.equals(v, b1.expression()) ? newExpression : v, Expression.class));
ConcatFunctionProcessorDefinition b2 = randomInstance();
Location newLoc = randomValueOtherThan(b2.location(), () -> randomLocation());
newB = new ConcatFunctionProcessorDefinition(
newLoc,
b2.expression(),
b2.left(),
b2.right());
assertEquals(newB,
b2.transformPropertiesOnly(v -> Objects.equals(v, b2.location()) ? newLoc : v, Location.class));
}
@Override
public void testReplaceChildren() {
ConcatFunctionProcessorDefinition b = randomInstance();
ProcessorDefinition newLeft = toProcessorDefinition((Expression) randomValueOtherThan(b.left(), () -> randomStringLiteral()));
ProcessorDefinition newRight = toProcessorDefinition((Expression) randomValueOtherThan(b.right(), () -> randomStringLiteral()));
ConcatFunctionProcessorDefinition newB =
new ConcatFunctionProcessorDefinition(b.location(), b.expression(), b.left(), b.right());
BinaryProcessorDefinition transformed = newB.replaceChildren(newLeft, b.right());
assertEquals(transformed.left(), newLeft);
assertEquals(transformed.location(), b.location());
assertEquals(transformed.expression(), b.expression());
assertEquals(transformed.right(), b.right());
transformed = newB.replaceChildren(b.left(), newRight);
assertEquals(transformed.left(), b.left());
assertEquals(transformed.location(), b.location());
assertEquals(transformed.expression(), b.expression());
assertEquals(transformed.right(), newRight);
transformed = newB.replaceChildren(newLeft, newRight);
assertEquals(transformed.left(), newLeft);
assertEquals(transformed.location(), b.location());
assertEquals(transformed.expression(), b.expression());
assertEquals(transformed.right(), newRight);
}
@Override
protected ConcatFunctionProcessorDefinition mutate(ConcatFunctionProcessorDefinition instance) {
List<Function<ConcatFunctionProcessorDefinition, ConcatFunctionProcessorDefinition>> randoms = new ArrayList<>();
randoms.add(f -> new ConcatFunctionProcessorDefinition(f.location(),
f.expression(),
toProcessorDefinition((Expression) randomValueOtherThan(f.left(), () -> randomStringLiteral())),
f.right()));
randoms.add(f -> new ConcatFunctionProcessorDefinition(f.location(),
f.expression(),
f.left(),
toProcessorDefinition((Expression) randomValueOtherThan(f.right(), () -> randomStringLiteral()))));
randoms.add(f -> new ConcatFunctionProcessorDefinition(f.location(),
f.expression(),
toProcessorDefinition((Expression) randomValueOtherThan(f.left(), () -> randomStringLiteral())),
toProcessorDefinition((Expression) randomValueOtherThan(f.right(), () -> randomStringLiteral()))));
return randomFrom(randoms).apply(instance);
}
@Override
protected ConcatFunctionProcessorDefinition copy(ConcatFunctionProcessorDefinition instance) {
return new ConcatFunctionProcessorDefinition(instance.location(),
instance.expression(),
instance.left(),
instance.right());
}
}

View File

@ -0,0 +1,57 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.expression.function.scalar.string;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.io.stream.Writeable.Reader;
import org.elasticsearch.test.AbstractWireSerializingTestCase;
import org.elasticsearch.xpack.sql.SqlIllegalArgumentException;
import org.elasticsearch.xpack.sql.expression.function.scalar.Processors;
import org.elasticsearch.xpack.sql.expression.function.scalar.processor.runtime.ConstantProcessor;
import static org.elasticsearch.xpack.sql.tree.Location.EMPTY;
import static org.elasticsearch.xpack.sql.expression.function.scalar.FunctionTestUtils.l;
public class ConcatProcessorTests extends AbstractWireSerializingTestCase<ConcatFunctionProcessor> {
@Override
protected ConcatFunctionProcessor createTestInstance() {
return new ConcatFunctionProcessor(
new ConstantProcessor(randomRealisticUnicodeOfLengthBetween(0, 128)),
new ConstantProcessor(randomRealisticUnicodeOfLengthBetween(0, 128)));
}
@Override
protected Reader<ConcatFunctionProcessor> instanceReader() {
return ConcatFunctionProcessor::new;
}
@Override
protected NamedWriteableRegistry getNamedWriteableRegistry() {
return new NamedWriteableRegistry(Processors.getNamedWriteables());
}
public void testConcatFunctionWithValidInput() {
assertEquals("foobar", new Concat(EMPTY, l("foo"), l("bar")).makeProcessorDefinition().asProcessor().process(null));
assertEquals("fb", new Concat(EMPTY, l('f'), l('b')).makeProcessorDefinition().asProcessor().process(null));
}
public void testConcatFunctionWithEdgeCases() {
assertEquals("foo", new Concat(EMPTY, l("foo"), l(null)).makeProcessorDefinition().asProcessor().process(null));
assertEquals("bar", new Concat(EMPTY, l(null), l("bar")).makeProcessorDefinition().asProcessor().process(null));
assertNull(new Concat(EMPTY, l(null), l(null)).makeProcessorDefinition().asProcessor().process(null));
}
public void testConcatFunctionInputsValidation() {
SqlIllegalArgumentException siae = expectThrows(SqlIllegalArgumentException.class,
() -> new Concat(EMPTY, l(5), l("foo")).makeProcessorDefinition().asProcessor().process(null));
assertEquals("A string/char is required; received [5]", siae.getMessage());
siae = expectThrows(SqlIllegalArgumentException.class,
() -> new Concat(EMPTY, l("foo bar"), l(3)).makeProcessorDefinition().asProcessor().process(null));
assertEquals("A string/char is required; received [3]", siae.getMessage());
}
}

View File

@ -0,0 +1,135 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.expression.function.scalar.string;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.function.scalar.FunctionTestUtils.Combinations;
import org.elasticsearch.xpack.sql.expression.function.scalar.processor.definition.ProcessorDefinition;
import org.elasticsearch.xpack.sql.tree.AbstractNodeTestCase;
import org.elasticsearch.xpack.sql.tree.Location;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import static org.elasticsearch.xpack.sql.expression.function.scalar.FunctionTestUtils.randomIntLiteral;
import static org.elasticsearch.xpack.sql.expression.function.scalar.FunctionTestUtils.randomStringLiteral;
import static org.elasticsearch.xpack.sql.expression.function.scalar.processor.definition.ProcessorDefinitions.toProcessorDefinition;
import static org.elasticsearch.xpack.sql.tree.LocationTests.randomLocation;
public class InsertFunctionProcessorDefinitionTests extends AbstractNodeTestCase<InsertFunctionProcessorDefinition, ProcessorDefinition> {
@Override
protected InsertFunctionProcessorDefinition randomInstance() {
return randomInsertFunctionProcessorDefinition();
}
private Expression randomInsertFunctionExpression() {
return randomInsertFunctionProcessorDefinition().expression();
}
public static InsertFunctionProcessorDefinition randomInsertFunctionProcessorDefinition() {
return (InsertFunctionProcessorDefinition) (new Insert(randomLocation(),
randomStringLiteral(),
randomIntLiteral(),
randomIntLiteral(),
randomStringLiteral())
.makeProcessorDefinition());
}
@Override
public void testTransform() {
// test transforming only the properties (location, expression),
// skipping the children (the two parameters of the binary function) which are tested separately
InsertFunctionProcessorDefinition b1 = randomInstance();
Expression newExpression = randomValueOtherThan(b1.expression(), () -> randomInsertFunctionExpression());
InsertFunctionProcessorDefinition newB = new InsertFunctionProcessorDefinition(
b1.location(),
newExpression,
b1.source(),
b1.start(),
b1.length(),
b1.replacement());
assertEquals(newB, b1.transformPropertiesOnly(v -> Objects.equals(v, b1.expression()) ? newExpression : v, Expression.class));
InsertFunctionProcessorDefinition b2 = randomInstance();
Location newLoc = randomValueOtherThan(b2.location(), () -> randomLocation());
newB = new InsertFunctionProcessorDefinition(
newLoc,
b2.expression(),
b2.source(),
b2.start(),
b2.length(),
b2.replacement());
assertEquals(newB,
b2.transformPropertiesOnly(v -> Objects.equals(v, b2.location()) ? newLoc : v, Location.class));
}
@Override
public void testReplaceChildren() {
InsertFunctionProcessorDefinition b = randomInstance();
ProcessorDefinition newSource = toProcessorDefinition((Expression) randomValueOtherThan(b.source(), () -> randomStringLiteral()));
ProcessorDefinition newStart = toProcessorDefinition((Expression) randomValueOtherThan(b.start(), () -> randomIntLiteral()));
ProcessorDefinition newLength = toProcessorDefinition((Expression) randomValueOtherThan(b.length(), () -> randomIntLiteral()));
ProcessorDefinition newR = toProcessorDefinition((Expression) randomValueOtherThan(b.replacement(), () -> randomStringLiteral()));
InsertFunctionProcessorDefinition newB =
new InsertFunctionProcessorDefinition(b.location(), b.expression(), b.source(), b.start(), b.length(), b.replacement());
InsertFunctionProcessorDefinition transformed = null;
// generate all the combinations of possible children modifications and test all of them
for(int i = 1; i < 5; i++) {
for(BitSet comb : new Combinations(4, i)) {
transformed = (InsertFunctionProcessorDefinition) newB.replaceChildren(
comb.get(0) ? newSource : b.source(),
comb.get(1) ? newStart : b.start(),
comb.get(2) ? newLength : b.length(),
comb.get(3) ? newR : b.replacement());
assertEquals(transformed.source(), comb.get(0) ? newSource : b.source());
assertEquals(transformed.start(), comb.get(1) ? newStart : b.start());
assertEquals(transformed.length(), comb.get(2) ? newLength : b.length());
assertEquals(transformed.replacement(), comb.get(3) ? newR : b.replacement());
assertEquals(transformed.expression(), b.expression());
assertEquals(transformed.location(), b.location());
}
}
}
@Override
protected InsertFunctionProcessorDefinition mutate(InsertFunctionProcessorDefinition instance) {
List<Function<InsertFunctionProcessorDefinition, InsertFunctionProcessorDefinition>> randoms = new ArrayList<>();
for(int i = 1; i < 5; i++) {
for(BitSet comb : new Combinations(4, i)) {
randoms.add(f -> new InsertFunctionProcessorDefinition(
f.location(),
f.expression(),
comb.get(0) ? toProcessorDefinition((Expression) randomValueOtherThan(f.source(),
() -> randomStringLiteral())) : f.source(),
comb.get(1) ? toProcessorDefinition((Expression) randomValueOtherThan(f.start(),
() -> randomIntLiteral())) : f.start(),
comb.get(2) ? toProcessorDefinition((Expression) randomValueOtherThan(f.length(),
() -> randomIntLiteral())): f.length(),
comb.get(3) ? toProcessorDefinition((Expression) randomValueOtherThan(f.replacement(),
() -> randomStringLiteral())) : f.replacement()));
}
}
return randomFrom(randoms).apply(instance);
}
@Override
protected InsertFunctionProcessorDefinition copy(InsertFunctionProcessorDefinition instance) {
return new InsertFunctionProcessorDefinition(instance.location(),
instance.expression(),
instance.source(),
instance.start(),
instance.length(),
instance.replacement());
}
}

View File

@ -0,0 +1,84 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.expression.function.scalar.string;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.io.stream.Writeable.Reader;
import org.elasticsearch.test.AbstractWireSerializingTestCase;
import org.elasticsearch.xpack.sql.SqlIllegalArgumentException;
import org.elasticsearch.xpack.sql.expression.function.scalar.Processors;
import org.elasticsearch.xpack.sql.expression.function.scalar.processor.runtime.ConstantProcessor;
import static org.elasticsearch.xpack.sql.tree.Location.EMPTY;
import static org.elasticsearch.xpack.sql.expression.function.scalar.FunctionTestUtils.l;
public class InsertProcessorTests extends AbstractWireSerializingTestCase<InsertFunctionProcessor> {
@Override
protected InsertFunctionProcessor createTestInstance() {
return new InsertFunctionProcessor(
new ConstantProcessor(randomRealisticUnicodeOfLengthBetween(0, 128)),
new ConstantProcessor(randomInt(256)),
new ConstantProcessor(randomInt(128)),
new ConstantProcessor(randomRealisticUnicodeOfLengthBetween(0, 256)));
}
@Override
protected Reader<InsertFunctionProcessor> instanceReader() {
return InsertFunctionProcessor::new;
}
@Override
protected NamedWriteableRegistry getNamedWriteableRegistry() {
return new NamedWriteableRegistry(Processors.getNamedWriteables());
}
public void testInsertWithValidInputs() {
assertEquals("bazbar", new Insert(EMPTY, l("foobar"), l(1), l(3), l("baz")).makeProcessorDefinition().asProcessor().process(null));
assertEquals("foobaz", new Insert(EMPTY, l("foobar"), l(4), l(3), l("baz")).makeProcessorDefinition().asProcessor().process(null));
}
public void testInsertWithEdgeCases() {
assertNull(new Insert(EMPTY, l(null), l(4), l(3), l("baz")).makeProcessorDefinition().asProcessor().process(null));
assertEquals("foobar", new Insert(EMPTY, l("foobar"), l(4), l(3), l(null)).makeProcessorDefinition().asProcessor().process(null));
assertEquals("foobar",
new Insert(EMPTY, l("foobar"), l(null), l(3), l("baz")).makeProcessorDefinition().asProcessor().process(null));
assertEquals("foobar",
new Insert(EMPTY, l("foobar"), l(4), l(null), l("baz")).makeProcessorDefinition().asProcessor().process(null));
assertEquals("bazbar", new Insert(EMPTY, l("foobar"), l(-1), l(3), l("baz")).makeProcessorDefinition().asProcessor().process(null));
assertEquals("foobaz", new Insert(EMPTY, l("foobar"), l(4), l(30), l("baz")).makeProcessorDefinition().asProcessor().process(null));
assertEquals("foobaz", new Insert(EMPTY, l("foobar"), l(6), l(1), l('z')).makeProcessorDefinition().asProcessor().process(null));
assertEquals("foobarbaz",
new Insert(EMPTY, l("foobar"), l(7), l(1000), l("baz")).makeProcessorDefinition().asProcessor().process(null));
assertEquals("foobar",
new Insert(EMPTY, l("foobar"), l(8), l(1000), l("baz")).makeProcessorDefinition().asProcessor().process(null));
assertEquals("fzr", new Insert(EMPTY, l("foobar"), l(2), l(4), l('z')).makeProcessorDefinition().asProcessor().process(null));
assertEquals("CAR", new Insert(EMPTY, l("FOOBAR"), l(1), l(5), l("CA")).makeProcessorDefinition().asProcessor().process(null));
assertEquals("z", new Insert(EMPTY, l('f'), l(1), l(10), l('z')).makeProcessorDefinition().asProcessor().process(null));
assertEquals("bla", new Insert(EMPTY, l(""), l(1), l(10), l("bla")).makeProcessorDefinition().asProcessor().process(null));
assertEquals("", new Insert(EMPTY, l(""), l(2), l(10), l("bla")).makeProcessorDefinition().asProcessor().process(null));
}
public void testInsertInputsValidation() {
SqlIllegalArgumentException siae = expectThrows(SqlIllegalArgumentException.class,
() -> new Insert(EMPTY, l(5), l(1), l(3), l("baz")).makeProcessorDefinition().asProcessor().process(null));
assertEquals("A string/char is required; received [5]", siae.getMessage());
siae = expectThrows(SqlIllegalArgumentException.class,
() -> new Insert(EMPTY, l("foobar"), l(1), l(3), l(66)).makeProcessorDefinition().asProcessor().process(null));
assertEquals("A string/char is required; received [66]", siae.getMessage());
siae = expectThrows(SqlIllegalArgumentException.class,
() -> new Insert(EMPTY, l("foobar"), l("c"), l(3), l("baz")).makeProcessorDefinition().asProcessor().process(null));
assertEquals("A number is required; received [c]", siae.getMessage());
siae = expectThrows(SqlIllegalArgumentException.class,
() -> new Insert(EMPTY, l("foobar"), l(1), l('z'), l("baz")).makeProcessorDefinition().asProcessor().process(null));
assertEquals("A number is required; received [z]", siae.getMessage());
siae = expectThrows(SqlIllegalArgumentException.class,
() -> new Insert(EMPTY, l("foobar"), l(1), l(-1), l("baz")).makeProcessorDefinition().asProcessor().process(null));
assertEquals("A positive number is required for [length]; received [-1]", siae.getMessage());
}
}

View File

@ -0,0 +1,169 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.expression.function.scalar.string;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.function.scalar.FunctionTestUtils.Combinations;
import org.elasticsearch.xpack.sql.expression.function.scalar.processor.definition.ProcessorDefinition;
import org.elasticsearch.xpack.sql.tree.AbstractNodeTestCase;
import org.elasticsearch.xpack.sql.tree.Location;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import static org.elasticsearch.xpack.sql.expression.function.scalar.FunctionTestUtils.randomIntLiteral;
import static org.elasticsearch.xpack.sql.expression.function.scalar.FunctionTestUtils.randomStringLiteral;
import static org.elasticsearch.xpack.sql.expression.function.scalar.processor.definition.ProcessorDefinitions.toProcessorDefinition;
import static org.elasticsearch.xpack.sql.tree.LocationTests.randomLocation;
public class LocateFunctionProcessorDefinitionTests extends AbstractNodeTestCase<LocateFunctionProcessorDefinition, ProcessorDefinition> {
@Override
protected LocateFunctionProcessorDefinition randomInstance() {
return randomLocateFunctionProcessorDefinition();
}
private Expression randomLocateFunctionExpression() {
return randomLocateFunctionProcessorDefinition().expression();
}
public static LocateFunctionProcessorDefinition randomLocateFunctionProcessorDefinition() {
return (LocateFunctionProcessorDefinition) (new Locate(randomLocation(),
randomStringLiteral(),
randomStringLiteral(),
frequently() ? randomIntLiteral() : null)
.makeProcessorDefinition());
}
@Override
public void testTransform() {
// test transforming only the properties (location, expression),
// skipping the children (the two parameters of the binary function) which are tested separately
LocateFunctionProcessorDefinition b1 = randomInstance();
Expression newExpression = randomValueOtherThan(b1.expression(), () -> randomLocateFunctionExpression());
LocateFunctionProcessorDefinition newB;
if (b1.start() == null) {
newB = new LocateFunctionProcessorDefinition(
b1.location(),
newExpression,
b1.pattern(),
b1.source());
} else {
newB = new LocateFunctionProcessorDefinition(
b1.location(),
newExpression,
b1.pattern(),
b1.source(),
b1.start());
}
assertEquals(newB, b1.transformPropertiesOnly(v -> Objects.equals(v, b1.expression()) ? newExpression : v, Expression.class));
LocateFunctionProcessorDefinition b2 = randomInstance();
Location newLoc = randomValueOtherThan(b2.location(), () -> randomLocation());
if (b2.start() == null) {
newB = new LocateFunctionProcessorDefinition(
newLoc,
b2.expression(),
b2.pattern(),
b2.source());
} else {
newB = new LocateFunctionProcessorDefinition(
newLoc,
b2.expression(),
b2.pattern(),
b2.source(),
b2.start());
}
assertEquals(newB,
b2.transformPropertiesOnly(v -> Objects.equals(v, b2.location()) ? newLoc : v, Location.class));
}
@Override
public void testReplaceChildren() {
LocateFunctionProcessorDefinition b = randomInstance();
ProcessorDefinition newPattern = toProcessorDefinition((Expression) randomValueOtherThan(b.pattern(), () -> randomStringLiteral()));
ProcessorDefinition newSource = toProcessorDefinition((Expression) randomValueOtherThan(b.source(), () -> randomStringLiteral()));
ProcessorDefinition newStart;
LocateFunctionProcessorDefinition newB;
if (b.start() == null) {
newB = new LocateFunctionProcessorDefinition(b.location(), b.expression(), b.pattern(), b.source());
newStart = null;
}
else {
newB = new LocateFunctionProcessorDefinition(b.location(), b.expression(), b.pattern(), b.source(), b.start());
newStart = toProcessorDefinition((Expression) randomValueOtherThan(b.start(), () -> randomIntLiteral()));
}
LocateFunctionProcessorDefinition transformed = null;
// generate all the combinations of possible children modifications and test all of them
for(int i = 1; i < 4; i++) {
for(BitSet comb : new Combinations(3, i)) {
transformed = (LocateFunctionProcessorDefinition) newB.replaceChildren(
comb.get(0) ? newPattern : b.pattern(),
comb.get(1) ? newSource : b.source(),
comb.get(2) ? newStart : b.start());
assertEquals(transformed.pattern(), comb.get(0) ? newPattern : b.pattern());
assertEquals(transformed.source(), comb.get(1) ? newSource : b.source());
assertEquals(transformed.start(), comb.get(2) ? newStart : b.start());
assertEquals(transformed.expression(), b.expression());
assertEquals(transformed.location(), b.location());
}
}
}
@Override
protected LocateFunctionProcessorDefinition mutate(LocateFunctionProcessorDefinition instance) {
List<Function<LocateFunctionProcessorDefinition, LocateFunctionProcessorDefinition>> randoms = new ArrayList<>();
if (instance.start() == null) {
for(int i = 1; i < 3; i++) {
for(BitSet comb : new Combinations(2, i)) {
randoms.add(f -> new LocateFunctionProcessorDefinition(f.location(),
f.expression(),
comb.get(0) ? toProcessorDefinition((Expression) randomValueOtherThan(f.pattern(),
() -> randomStringLiteral())) : f.pattern(),
comb.get(1) ? toProcessorDefinition((Expression) randomValueOtherThan(f.source(),
() -> randomStringLiteral())) : f.source()));
}
}
} else {
for(int i = 1; i < 4; i++) {
for(BitSet comb : new Combinations(3, i)) {
randoms.add(f -> new LocateFunctionProcessorDefinition(f.location(),
f.expression(),
comb.get(0) ? toProcessorDefinition((Expression) randomValueOtherThan(f.pattern(),
() -> randomStringLiteral())) : f.pattern(),
comb.get(1) ? toProcessorDefinition((Expression) randomValueOtherThan(f.source(),
() -> randomStringLiteral())) : f.source(),
comb.get(2) ? toProcessorDefinition((Expression) randomValueOtherThan(f.start(),
() -> randomIntLiteral())) : f.start()));
}
}
}
return randomFrom(randoms).apply(instance);
}
@Override
protected LocateFunctionProcessorDefinition copy(LocateFunctionProcessorDefinition instance) {
return instance.start() == null ?
new LocateFunctionProcessorDefinition(instance.location(),
instance.expression(),
instance.pattern(),
instance.source())
:
new LocateFunctionProcessorDefinition(instance.location(),
instance.expression(),
instance.pattern(),
instance.source(),
instance.start());
}
}

View File

@ -0,0 +1,71 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.expression.function.scalar.string;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.io.stream.Writeable.Reader;
import org.elasticsearch.test.AbstractWireSerializingTestCase;
import org.elasticsearch.xpack.sql.SqlIllegalArgumentException;
import org.elasticsearch.xpack.sql.expression.function.scalar.Processors;
import org.elasticsearch.xpack.sql.expression.function.scalar.processor.runtime.ConstantProcessor;
import static org.elasticsearch.xpack.sql.tree.Location.EMPTY;
import static org.elasticsearch.xpack.sql.expression.function.scalar.FunctionTestUtils.l;
public class LocateProcessorTests extends AbstractWireSerializingTestCase<LocateFunctionProcessor> {
@Override
protected LocateFunctionProcessor createTestInstance() {
// the "start" parameter is optional and is treated as null in the constructor
// when it is not used. Need to take this into account when generating random
// values for it.
Integer start = frequently() ? randomInt() : null;
return new LocateFunctionProcessor(
new ConstantProcessor(randomRealisticUnicodeOfLengthBetween(0, 128)),
new ConstantProcessor(randomRealisticUnicodeOfLengthBetween(0, 128)),
new ConstantProcessor(start));
}
@Override
protected Reader<LocateFunctionProcessor> instanceReader() {
return LocateFunctionProcessor::new;
}
@Override
protected NamedWriteableRegistry getNamedWriteableRegistry() {
return new NamedWriteableRegistry(Processors.getNamedWriteables());
}
public void testLocateFunctionWithValidInput() {
assertEquals(4, new Locate(EMPTY, l("bar"), l("foobarbar"), l(null)).makeProcessorDefinition().asProcessor().process(null));
assertEquals(7, new Locate(EMPTY, l("bar"), l("foobarbar"), l(5)).makeProcessorDefinition().asProcessor().process(null));
}
public void testLocateFunctionWithEdgeCasesInputs() {
assertEquals(4, new Locate(EMPTY, l("bar"), l("foobarbar"), l(null)).makeProcessorDefinition().asProcessor().process(null));
assertNull(new Locate(EMPTY, l("bar"), l(null), l(3)).makeProcessorDefinition().asProcessor().process(null));
assertEquals(0, new Locate(EMPTY, l(null), l("foobarbar"), l(null)).makeProcessorDefinition().asProcessor().process(null));
assertEquals(0, new Locate(EMPTY, l(null), l("foobarbar"), l(null)).makeProcessorDefinition().asProcessor().process(null));
assertEquals(1, new Locate(EMPTY, l("foo"), l("foobarbar"), l(null)).makeProcessorDefinition().asProcessor().process(null));
assertEquals(1, new Locate(EMPTY, l('o'), l('o'), l(null)).makeProcessorDefinition().asProcessor().process(null));
assertEquals(9, new Locate(EMPTY, l('r'), l("foobarbar"), l(9)).makeProcessorDefinition().asProcessor().process(null));
assertEquals(4, new Locate(EMPTY, l("bar"), l("foobarbar"), l(-3)).makeProcessorDefinition().asProcessor().process(null));
}
public void testLocateFunctionValidatingInputs() {
SqlIllegalArgumentException siae = expectThrows(SqlIllegalArgumentException.class,
() -> new Locate(EMPTY, l(5), l("foobarbar"), l(3)).makeProcessorDefinition().asProcessor().process(null));
assertEquals("A string/char is required; received [5]", siae.getMessage());
siae = expectThrows(SqlIllegalArgumentException.class,
() -> new Locate(EMPTY, l("foo"), l(1), l(3)).makeProcessorDefinition().asProcessor().process(null));
assertEquals("A string/char is required; received [1]", siae.getMessage());
siae = expectThrows(SqlIllegalArgumentException.class,
() -> new Locate(EMPTY, l("foobarbar"), l("bar"), l('c')).makeProcessorDefinition().asProcessor().process(null));
assertEquals("A number is required; received [c]", siae.getMessage());
}
}

View File

@ -0,0 +1,126 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.expression.function.scalar.string;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.function.scalar.FunctionTestUtils.Combinations;
import org.elasticsearch.xpack.sql.expression.function.scalar.processor.definition.ProcessorDefinition;
import org.elasticsearch.xpack.sql.tree.AbstractNodeTestCase;
import org.elasticsearch.xpack.sql.tree.Location;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import static org.elasticsearch.xpack.sql.expression.function.scalar.FunctionTestUtils.randomStringLiteral;
import static org.elasticsearch.xpack.sql.expression.function.scalar.processor.definition.ProcessorDefinitions.toProcessorDefinition;
import static org.elasticsearch.xpack.sql.tree.LocationTests.randomLocation;
public class ReplaceFunctionProcessorDefinitionTests extends AbstractNodeTestCase<ReplaceFunctionProcessorDefinition, ProcessorDefinition> {
@Override
protected ReplaceFunctionProcessorDefinition randomInstance() {
return randomReplaceFunctionProcessorDefinition();
}
private Expression randomReplaceFunctionExpression() {
return randomReplaceFunctionProcessorDefinition().expression();
}
public static ReplaceFunctionProcessorDefinition randomReplaceFunctionProcessorDefinition() {
return (ReplaceFunctionProcessorDefinition) (new Replace(randomLocation(),
randomStringLiteral(),
randomStringLiteral(),
randomStringLiteral())
.makeProcessorDefinition());
}
@Override
public void testTransform() {
// test transforming only the properties (location, expression),
// skipping the children (the two parameters of the binary function) which are tested separately
ReplaceFunctionProcessorDefinition b1 = randomInstance();
Expression newExpression = randomValueOtherThan(b1.expression(), () -> randomReplaceFunctionExpression());
ReplaceFunctionProcessorDefinition newB = new ReplaceFunctionProcessorDefinition(
b1.location(),
newExpression,
b1.source(),
b1.pattern(),
b1.replacement());
assertEquals(newB, b1.transformPropertiesOnly(v -> Objects.equals(v, b1.expression()) ? newExpression : v, Expression.class));
ReplaceFunctionProcessorDefinition b2 = randomInstance();
Location newLoc = randomValueOtherThan(b2.location(), () -> randomLocation());
newB = new ReplaceFunctionProcessorDefinition(
newLoc,
b2.expression(),
b2.source(),
b2.pattern(),
b2.replacement());
assertEquals(newB,
b2.transformPropertiesOnly(v -> Objects.equals(v, b2.location()) ? newLoc : v, Location.class));
}
@Override
public void testReplaceChildren() {
ReplaceFunctionProcessorDefinition b = randomInstance();
ProcessorDefinition newSource = toProcessorDefinition((Expression) randomValueOtherThan(b.source(), () -> randomStringLiteral()));
ProcessorDefinition newPattern = toProcessorDefinition((Expression) randomValueOtherThan(b.pattern(), () -> randomStringLiteral()));
ProcessorDefinition newR = toProcessorDefinition((Expression) randomValueOtherThan(b.replacement(), () -> randomStringLiteral()));
ReplaceFunctionProcessorDefinition newB =
new ReplaceFunctionProcessorDefinition(b.location(), b.expression(), b.source(), b.pattern(), b.replacement());
ReplaceFunctionProcessorDefinition transformed = null;
// generate all the combinations of possible children modifications and test all of them
for(int i = 1; i < 4; i++) {
for(BitSet comb : new Combinations(3, i)) {
transformed = (ReplaceFunctionProcessorDefinition) newB.replaceChildren(
comb.get(0) ? newSource : b.source(),
comb.get(1) ? newPattern : b.pattern(),
comb.get(2) ? newR : b.replacement());
assertEquals(transformed.source(), comb.get(0) ? newSource : b.source());
assertEquals(transformed.pattern(), comb.get(1) ? newPattern : b.pattern());
assertEquals(transformed.replacement(), comb.get(2) ? newR : b.replacement());
assertEquals(transformed.expression(), b.expression());
assertEquals(transformed.location(), b.location());
}
}
}
@Override
protected ReplaceFunctionProcessorDefinition mutate(ReplaceFunctionProcessorDefinition instance) {
List<Function<ReplaceFunctionProcessorDefinition, ReplaceFunctionProcessorDefinition>> randoms = new ArrayList<>();
for(int i = 1; i < 4; i++) {
for(BitSet comb : new Combinations(3, i)) {
randoms.add(f -> new ReplaceFunctionProcessorDefinition(f.location(),
f.expression(),
comb.get(0) ? toProcessorDefinition((Expression) randomValueOtherThan(f.source(),
() -> randomStringLiteral())) : f.source(),
comb.get(1) ? toProcessorDefinition((Expression) randomValueOtherThan(f.pattern(),
() -> randomStringLiteral())) : f.pattern(),
comb.get(2) ? toProcessorDefinition((Expression) randomValueOtherThan(f.replacement(),
() -> randomStringLiteral())) : f.replacement()));
}
}
return randomFrom(randoms).apply(instance);
}
@Override
protected ReplaceFunctionProcessorDefinition copy(ReplaceFunctionProcessorDefinition instance) {
return new ReplaceFunctionProcessorDefinition(instance.location(),
instance.expression(),
instance.source(),
instance.pattern(),
instance.replacement());
}
}

View File

@ -0,0 +1,66 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.expression.function.scalar.string;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.io.stream.Writeable.Reader;
import org.elasticsearch.test.AbstractWireSerializingTestCase;
import org.elasticsearch.xpack.sql.SqlIllegalArgumentException;
import org.elasticsearch.xpack.sql.expression.function.scalar.Processors;
import org.elasticsearch.xpack.sql.expression.function.scalar.processor.runtime.ConstantProcessor;
import static org.elasticsearch.xpack.sql.tree.Location.EMPTY;
import static org.elasticsearch.xpack.sql.expression.function.scalar.FunctionTestUtils.l;
public class ReplaceProcessorTests extends AbstractWireSerializingTestCase<ReplaceFunctionProcessor> {
@Override
protected ReplaceFunctionProcessor createTestInstance() {
return new ReplaceFunctionProcessor(
new ConstantProcessor(randomRealisticUnicodeOfLengthBetween(0, 128)),
new ConstantProcessor(randomRealisticUnicodeOfLengthBetween(0, 128)),
new ConstantProcessor(randomRealisticUnicodeOfLengthBetween(0, 128)));
}
@Override
protected Reader<ReplaceFunctionProcessor> instanceReader() {
return ReplaceFunctionProcessor::new;
}
@Override
protected NamedWriteableRegistry getNamedWriteableRegistry() {
return new NamedWriteableRegistry(Processors.getNamedWriteables());
}
public void testReplaceFunctionWithValidInput() {
assertEquals("foobazbaz",
new Replace(EMPTY, l("foobarbar"), l("bar"), l("baz")).makeProcessorDefinition().asProcessor().process(null));
assertEquals("foobXrbXr", new Replace(EMPTY, l("foobarbar"), l('a'), l('X')).makeProcessorDefinition().asProcessor().process(null));
assertEquals("z", new Replace(EMPTY, l('f'), l('f'), l('z')).makeProcessorDefinition().asProcessor().process(null));
}
public void testReplaceFunctionWithEdgeCases() {
assertEquals("foobarbar",
new Replace(EMPTY, l("foobarbar"), l("bar"), l(null)).makeProcessorDefinition().asProcessor().process(null));
assertEquals("foobarbar",
new Replace(EMPTY, l("foobarbar"), l(null), l("baz")).makeProcessorDefinition().asProcessor().process(null));
assertNull(new Replace(EMPTY, l(null), l("bar"), l("baz")).makeProcessorDefinition().asProcessor().process(null));
assertNull(new Replace(EMPTY, l(null), l(null), l(null)).makeProcessorDefinition().asProcessor().process(null));
}
public void testReplaceFunctionInputsValidation() {
SqlIllegalArgumentException siae = expectThrows(SqlIllegalArgumentException.class,
() -> new Replace(EMPTY, l(5), l("bar"), l("baz")).makeProcessorDefinition().asProcessor().process(null));
assertEquals("A string/char is required; received [5]", siae.getMessage());
siae = expectThrows(SqlIllegalArgumentException.class,
() -> new Replace(EMPTY, l("foobarbar"), l(4), l("baz")).makeProcessorDefinition().asProcessor().process(null));
assertEquals("A string/char is required; received [4]", siae.getMessage());
siae = expectThrows(SqlIllegalArgumentException.class,
() -> new Replace(EMPTY, l("foobarbar"), l("bar"), l(3)).makeProcessorDefinition().asProcessor().process(null));
assertEquals("A string/char is required; received [3]", siae.getMessage());
}
}

View File

@ -0,0 +1,127 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.expression.function.scalar.string;
import org.elasticsearch.xpack.sql.expression.Expression;
import org.elasticsearch.xpack.sql.expression.function.scalar.FunctionTestUtils.Combinations;
import org.elasticsearch.xpack.sql.expression.function.scalar.processor.definition.ProcessorDefinition;
import org.elasticsearch.xpack.sql.tree.AbstractNodeTestCase;
import org.elasticsearch.xpack.sql.tree.Location;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
import static org.elasticsearch.xpack.sql.expression.function.scalar.FunctionTestUtils.randomIntLiteral;
import static org.elasticsearch.xpack.sql.expression.function.scalar.FunctionTestUtils.randomStringLiteral;
import static org.elasticsearch.xpack.sql.expression.function.scalar.processor.definition.ProcessorDefinitions.toProcessorDefinition;
import static org.elasticsearch.xpack.sql.tree.LocationTests.randomLocation;
public class SubstringFunctionProcessorDefinitionTests
extends AbstractNodeTestCase<SubstringFunctionProcessorDefinition, ProcessorDefinition> {
@Override
protected SubstringFunctionProcessorDefinition randomInstance() {
return randomSubstringFunctionProcessorDefinition();
}
private Expression randomSubstringFunctionExpression() {
return randomSubstringFunctionProcessorDefinition().expression();
}
public static SubstringFunctionProcessorDefinition randomSubstringFunctionProcessorDefinition() {
return (SubstringFunctionProcessorDefinition) (new Substring(randomLocation(),
randomStringLiteral(),
randomIntLiteral(),
randomIntLiteral())
.makeProcessorDefinition());
}
@Override
public void testTransform() {
// test transforming only the properties (location, expression),
// skipping the children (the two parameters of the binary function) which are tested separately
SubstringFunctionProcessorDefinition b1 = randomInstance();
Expression newExpression = randomValueOtherThan(b1.expression(), () -> randomSubstringFunctionExpression());
SubstringFunctionProcessorDefinition newB = new SubstringFunctionProcessorDefinition(
b1.location(),
newExpression,
b1.source(),
b1.start(),
b1.length());
assertEquals(newB, b1.transformPropertiesOnly(v -> Objects.equals(v, b1.expression()) ? newExpression : v, Expression.class));
SubstringFunctionProcessorDefinition b2 = randomInstance();
Location newLoc = randomValueOtherThan(b2.location(), () -> randomLocation());
newB = new SubstringFunctionProcessorDefinition(
newLoc,
b2.expression(),
b2.source(),
b2.start(),
b2.length());
assertEquals(newB,
b2.transformPropertiesOnly(v -> Objects.equals(v, b2.location()) ? newLoc : v, Location.class));
}
@Override
public void testReplaceChildren() {
SubstringFunctionProcessorDefinition b = randomInstance();
ProcessorDefinition newSource = toProcessorDefinition((Expression) randomValueOtherThan(b.source(), () -> randomStringLiteral()));
ProcessorDefinition newStart = toProcessorDefinition((Expression) randomValueOtherThan(b.start(), () -> randomIntLiteral()));
ProcessorDefinition newLength = toProcessorDefinition((Expression) randomValueOtherThan(b.length(), () -> randomIntLiteral()));
SubstringFunctionProcessorDefinition newB =
new SubstringFunctionProcessorDefinition(b.location(), b.expression(), b.source(), b.start(), b.length());
SubstringFunctionProcessorDefinition transformed = null;
// generate all the combinations of possible children modifications and test all of them
for(int i = 1; i < 4; i++) {
for(BitSet comb : new Combinations(3, i)) {
transformed = (SubstringFunctionProcessorDefinition) newB.replaceChildren(
comb.get(0) ? newSource : b.source(),
comb.get(1) ? newStart : b.start(),
comb.get(2) ? newLength : b.length());
assertEquals(transformed.source(), comb.get(0) ? newSource : b.source());
assertEquals(transformed.start(), comb.get(1) ? newStart : b.start());
assertEquals(transformed.length(), comb.get(2) ? newLength : b.length());
assertEquals(transformed.expression(), b.expression());
assertEquals(transformed.location(), b.location());
}
}
}
@Override
protected SubstringFunctionProcessorDefinition mutate(SubstringFunctionProcessorDefinition instance) {
List<Function<SubstringFunctionProcessorDefinition, SubstringFunctionProcessorDefinition>> randoms = new ArrayList<>();
for(int i = 1; i < 4; i++) {
for(BitSet comb : new Combinations(3, i)) {
randoms.add(f -> new SubstringFunctionProcessorDefinition(
f.location(),
f.expression(),
comb.get(0) ? toProcessorDefinition((Expression) randomValueOtherThan(f.source(),
() -> randomStringLiteral())) : f.source(),
comb.get(1) ? toProcessorDefinition((Expression) randomValueOtherThan(f.start(),
() -> randomIntLiteral())) : f.start(),
comb.get(2) ? toProcessorDefinition((Expression) randomValueOtherThan(f.length(),
() -> randomIntLiteral())): f.length()));
}
}
return randomFrom(randoms).apply(instance);
}
@Override
protected SubstringFunctionProcessorDefinition copy(SubstringFunctionProcessorDefinition instance) {
return new SubstringFunctionProcessorDefinition(instance.location(),
instance.expression(),
instance.source(),
instance.start(),
instance.length());
}
}

View File

@ -0,0 +1,75 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
package org.elasticsearch.xpack.sql.expression.function.scalar.string;
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
import org.elasticsearch.common.io.stream.Writeable.Reader;
import org.elasticsearch.test.AbstractWireSerializingTestCase;
import org.elasticsearch.xpack.sql.SqlIllegalArgumentException;
import org.elasticsearch.xpack.sql.expression.function.scalar.Processors;
import org.elasticsearch.xpack.sql.expression.function.scalar.processor.runtime.ConstantProcessor;
import static org.elasticsearch.xpack.sql.tree.Location.EMPTY;
import static org.elasticsearch.xpack.sql.expression.function.scalar.FunctionTestUtils.l;
public class SubstringProcessorTests extends AbstractWireSerializingTestCase<SubstringFunctionProcessor> {
@Override
protected SubstringFunctionProcessor createTestInstance() {
return new SubstringFunctionProcessor(
new ConstantProcessor(randomRealisticUnicodeOfLengthBetween(0, 256)),
new ConstantProcessor(randomInt(256)),
new ConstantProcessor(randomInt(256)));
}
@Override
protected Reader<SubstringFunctionProcessor> instanceReader() {
return SubstringFunctionProcessor::new;
}
@Override
protected NamedWriteableRegistry getNamedWriteableRegistry() {
return new NamedWriteableRegistry(Processors.getNamedWriteables());
}
public void testSubstringFunctionWithValidInput() {
assertEquals("bar", new Substring(EMPTY, l("foobarbar"), l(4), l(3)).makeProcessorDefinition().asProcessor().process(null));
assertEquals("foo", new Substring(EMPTY, l("foobarbar"), l(1), l(3)).makeProcessorDefinition().asProcessor().process(null));
assertEquals("baz", new Substring(EMPTY, l("foobarbaz"), l(7), l(3)).makeProcessorDefinition().asProcessor().process(null));
assertEquals("f", new Substring(EMPTY, l('f'), l(1), l(1)).makeProcessorDefinition().asProcessor().process(null));
}
public void testSubstringFunctionWithEdgeCases() {
assertEquals("foobarbar",
new Substring(EMPTY, l("foobarbar"), l(1), l(null)).makeProcessorDefinition().asProcessor().process(null));
assertEquals("foobarbar",
new Substring(EMPTY, l("foobarbar"), l(null), l(3)).makeProcessorDefinition().asProcessor().process(null));
assertNull(new Substring(EMPTY, l(null), l(1), l(3)).makeProcessorDefinition().asProcessor().process(null));
assertNull(new Substring(EMPTY, l(null), l(null), l(null)).makeProcessorDefinition().asProcessor().process(null));
assertEquals("foo", new Substring(EMPTY, l("foobarbar"), l(-5), l(3)).makeProcessorDefinition().asProcessor().process(null));
assertEquals("barbar", new Substring(EMPTY, l("foobarbar"), l(4), l(30)).makeProcessorDefinition().asProcessor().process(null));
assertEquals("r", new Substring(EMPTY, l("foobarbar"), l(9), l(1)).makeProcessorDefinition().asProcessor().process(null));
assertEquals("", new Substring(EMPTY, l("foobarbar"), l(10), l(1)).makeProcessorDefinition().asProcessor().process(null));
assertEquals("", new Substring(EMPTY, l("foobarbar"), l(123), l(3)).makeProcessorDefinition().asProcessor().process(null));
}
public void testSubstringFunctionInputsValidation() {
SqlIllegalArgumentException siae = expectThrows(SqlIllegalArgumentException.class,
() -> new Substring(EMPTY, l(5), l(1), l(3)).makeProcessorDefinition().asProcessor().process(null));
assertEquals("A string/char is required; received [5]", siae.getMessage());
siae = expectThrows(SqlIllegalArgumentException.class,
() -> new Substring(EMPTY, l("foobarbar"), l(1), l("baz")).makeProcessorDefinition().asProcessor().process(null));
assertEquals("A number is required; received [baz]", siae.getMessage());
siae = expectThrows(SqlIllegalArgumentException.class,
() -> new Substring(EMPTY, l("foobarbar"), l("bar"), l(3)).makeProcessorDefinition().asProcessor().process(null));
assertEquals("A number is required; received [bar]", siae.getMessage());
siae = expectThrows(SqlIllegalArgumentException.class,
() -> new Substring(EMPTY, l("foobarbar"), l(1), l(-3)).makeProcessorDefinition().asProcessor().process(null));
assertEquals("A positive number is required for [length]; received [-3]", siae.getMessage());
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
@ -51,6 +51,8 @@ public abstract class ShowTestCase extends CliIntegrationTestCase {
assertThat(readLine(), RegexMatcher.matches("\\s*LCASE\\s*\\|\\s*SCALAR\\s*"));
assertThat(readLine(), RegexMatcher.matches("\\s*LENGTH\\s*\\|\\s*SCALAR\\s*"));
assertThat(readLine(), RegexMatcher.matches("\\s*LTRIM\\s*\\|\\s*SCALAR\\s*"));
assertThat(readLine(), RegexMatcher.matches("\\s*LEFT\\s*\\|\\s*SCALAR\\s*"));
assertThat(readLine(), RegexMatcher.matches("\\s*LOCATE\\s*\\|\\s*SCALAR\\s*"));
assertEquals("", readLine());
}

View File

@ -145,6 +145,8 @@ public final class CsvTestUtils {
return "double";
case "ts":
return "timestamp";
case "bt":
return "byte";
default:
return type;
}

View File

@ -78,6 +78,15 @@ LENGTH |SCALAR
LTRIM |SCALAR
RTRIM |SCALAR
SPACE |SCALAR
CONCAT |SCALAR
INSERT |SCALAR
LEFT |SCALAR
LOCATE |SCALAR
POSITION |SCALAR
REPEAT |SCALAR
REPLACE |SCALAR
RIGHT |SCALAR
SUBSTRING |SCALAR
UCASE |SCALAR
SCORE |SCORE
;

View File

@ -232,6 +232,15 @@ LTRIM |SCALAR
RTRIM |SCALAR
SPACE |SCALAR
UCASE |SCALAR
CONCAT |SCALAR
INSERT |SCALAR
LEFT |SCALAR
LOCATE |SCALAR
POSITION |SCALAR
REPEAT |SCALAR
REPLACE |SCALAR
RIGHT |SCALAR
SUBSTRING |SCALAR
SCORE |SCORE
// end::showFunctions

View File

@ -12,7 +12,7 @@ SELECT COUNT(*) FROM "emp";
// list of <ColumnName:ColumnType*>
// type might be missing in which case it will be autodetected or can be one of the following
// d - double, f - float, i - int, b - byte, l - long, t - timestamp, date
// d - double, f - float, i - int, bt - byte, b - boolean, l - long, t - timestamp, date
A,B:d,C:i
// actual values
foo,2.5,3

View File

@ -28,3 +28,382 @@ len:i | first_name:s
72 |Prasadram
88 |Sreekrishna
;
selectConcatWithOrderBy
SELECT first_name f, last_name l, CONCAT(first_name,last_name) cct FROM test_emp ORDER BY CONCAT(first_name,last_name) LIMIT 10;
f:s | l:s | cct:s
Adamantios | Portugali | AdamantiosPortugali
Alain | Chappelet | AlainChappelet
Alejandro | Brender | AlejandroBrender
Alejandro | McAlpine | AlejandroMcAlpine
Amabile | Gomatam | AmabileGomatam
Anneke | Preusig | AnnekePreusig
Anoosh | Peyn | AnooshPeyn
Arif | Merlo | ArifMerlo
Arumugam | Ossenbruggen | ArumugamOssenbruggen
Bader | Swan | BaderSwan
;
selectNestedConcatWithOrderBy
SELECT first_name f, last_name l, CONCAT(first_name,CONCAT(' ',last_name)) cct FROM test_emp ORDER BY CONCAT(first_name,CONCAT(' ',last_name)) LIMIT 10;
f:s | l:s | cct:s
Adamantios | Portugali | Adamantios Portugali
Alain | Chappelet | Alain Chappelet
Alejandro | Brender | Alejandro Brender
Alejandro | McAlpine | Alejandro McAlpine
Amabile | Gomatam | Amabile Gomatam
Anneke | Preusig | Anneke Preusig
Anoosh | Peyn | Anoosh Peyn
Arif | Merlo | Arif Merlo
Arumugam | Ossenbruggen | Arumugam Ossenbruggen
Bader | Swan | Bader Swan
;
selectConcatWithGroupBy
SELECT CONCAT(first_name,last_name) cct FROM test_emp GROUP BY CONCAT(first_name,last_name) ORDER BY CONCAT(first_name,last_name) LIMIT 1;
cct:s
AdamantiosPortugali
;
selectAsciiOfConcatWithGroupByOrderByCount
SELECT ASCII(CONCAT("first_name","last_name")) ascii, COUNT(*) count FROM "test_emp" GROUP BY ASCII(CONCAT("first_name","last_name")) ORDER BY ASCII(CONCAT("first_name","last_name")) DESC LIMIT 10;
ascii:i | count:l
90 | 2
89 | 3
88 | 1
87 | 1
86 | 3
85 | 2
84 | 3
83 | 11
82 | 3
80 | 6
;
selectRepeatTwice
SELECT "first_name" orig, REPEAT("first_name",2) reps FROM "test_emp" WHERE ASCII("first_name")=65 ORDER BY REPEAT("first_name",2) ASC LIMIT 10;
orig:s | reps:s
Adamantios | AdamantiosAdamantios
Alain | AlainAlain
Alejandro | AlejandroAlejandro
Alejandro | AlejandroAlejandro
Amabile | AmabileAmabile
Anneke | AnnekeAnneke
Anoosh | AnooshAnoosh
Arif | ArifArif
Arumugam | ArumugamArumugam
;
selectInsertWithLcase
SELECT "first_name" orig, INSERT("first_name",2,1000,LCASE("first_name")) modified FROM "test_emp" WHERE ASCII("first_name")=65 ORDER BY "first_name" ASC LIMIT 10;
orig:s | modified:s
Adamantios | Aadamantios
Alain | Aalain
Alejandro | Aalejandro
Alejandro | Aalejandro
Amabile | Aamabile
Anneke | Aanneke
Anoosh | Aanoosh
Arif | Aarif
Arumugam | Aarumugam
;
selectInsertWithLcaseAndLengthWithOrderBy
SELECT "first_name" origFN, "last_name" origLN, INSERT(UCASE("first_name"),LENGTH("first_name")+1,123,LCASE("last_name")) modified FROM "test_emp" WHERE ASCII("first_name")=65 ORDER BY "first_name" ASC LIMIT 10;
origFN:s | origLN:s | modified:s
Adamantios | Portugali | ADAMANTIOSportugali
Alain | Chappelet | ALAINchappelet
Alejandro | Brender | ALEJANDRObrender
Alejandro | McAlpine | ALEJANDROmcalpine
Amabile | Gomatam | AMABILEgomatam
Anneke | Preusig | ANNEKEpreusig
Anoosh | Peyn | ANOOSHpeyn
Arif | Merlo | ARIFmerlo
Arumugam | Ossenbruggen | ARUMUGAMossenbruggen
;
selectInsertWithUcaseWithGroupByAndOrderBy
SELECT INSERT(UCASE("first_name"),2,123000,INSERT(UCASE("last_name"),2,500,'')) modified, COUNT(*) count FROM "test_emp" WHERE ASCII("first_name")=65 GROUP BY INSERT(UCASE("first_name"),2,123000,INSERT(UCASE("last_name"),2,500,'')) ORDER BY INSERT(UCASE("first_name"),2,123000,INSERT(UCASE("last_name"),2,500,'')) ASC LIMIT 10;
modified:s | count:l
AB | 1
AC | 1
AG | 1
AM | 2
AO | 1
AP | 3
;
selectSubstringWithGroupBy
SELECT SUBSTRING("first_name",1,2) modified, COUNT(*) count FROM "test_emp" WHERE ASCII("first_name")=65 GROUP BY SUBSTRING("first_name",1,2) ORDER BY SUBSTRING("first_name",1,2) ASC LIMIT 10;
modified:s | count:l
Ad | 1
Al | 3
Am | 1
An | 2
Ar | 2
;
selectSubstringWithWhereCountAndGroupBy
SELECT SUBSTRING("first_name",1,2) modified, COUNT(*) count FROM "test_emp" WHERE SUBSTRING("first_name",1,2)='Al' GROUP BY SUBSTRING("first_name",1,2) LIMIT 10;
modified:s | count:l
Al | 3
;
//this one doesn't return anything. The problem is "IS NOT NULL". GH issue created to generally investigate the NULLs usage: https://github.com/elastic/elasticsearch/issues/32079
//selectSubstringWithWhereNotNullAndCountGroupBy
//SELECT SUBSTRING("first_name",5,20) modified, COUNT(*) count FROM "test_emp" WHERE SUBSTRING("first_name",5,20) IS NOT NULL GROUP BY SUBSTRING("first_name",5,20) ORDER BY SUBSTRING("first_name",5,20) LIMIT 10;
// modified:s | count:l
//---------------+---------------
// |15
//adram |1
//af |1
//aja |1
//al |1
//andro |2
//antios |1
//ard |1
//areta |1
//arsan |1
//;
selectSubstringWithWhereAndGroupBy
SELECT SUBSTRING("first_name",5,20) modified, COUNT(*) count FROM "test_emp" GROUP BY SUBSTRING("first_name",5,20) ORDER BY SUBSTRING("first_name",5,20) LIMIT 10;
modified:s | count:l
---------------+---------------
|15
adram |1
af |1
aja |1
al |1
andro |2
antios |1
ard |1
areta |1
arsan |1
;
selectReplace
SELECT REPLACE("first_name",'A','X') modified, "first_name" origFN FROM "test_emp" ORDER BY "first_name" LIMIT 10;
modified:s | origFN:s
---------------+---------------
Xdamantios |Adamantios
Xlain |Alain
Xlejandro |Alejandro
Xlejandro |Alejandro
Xmabile |Amabile
Xnneke |Anneke
Xnoosh |Anoosh
Xrif |Arif
Xrumugam |Arumugam
Bader |Bader
;
selectReplaceWithGroupBy
SELECT REPLACE("first_name",'jan','_JAN_') modified, COUNT(*) count FROM "test_emp" GROUP BY REPLACE("first_name",'jan','_JAN_') ORDER BY REPLACE("first_name",'jan','_JAN_') LIMIT 10;
modified:s | count:l
---------------+---------------
Adamantios |1
Alain |1
Ale_JAN_dro |2
Amabile |1
Anneke |1
Anoosh |1
Arif |1
Arumugam |1
Bader |1
Basil |1
;
selectReplaceWithCastAndCondition
SELECT REPLACE(CAST("languages" AS VARCHAR),'1','100') foo, "languages" FROM "test_emp" WHERE "languages"=1 OR "languages"=2 LIMIT 5;
foo:s | languages:bt
---------------+---------------
2 |2
100 |1
2 |2
100 |1
100 |1
;
selectPositionWithConditionAndLcase
SELECT POSITION('x',LCASE("first_name")) pos, "first_name" FROM "test_emp" WHERE POSITION('x',LCASE("first_name")) != 0;
pos:i | first_name:s
---------------+---------------
4 |Guoxiang
1 |Xinglin
;
selectPositionWithLcaseAndConditionWithGroupByAndOrderBy
SELECT POSITION('m',LCASE("first_name")), COUNT(*) pos FROM "test_emp" WHERE POSITION('m',LCASE("first_name")) != 0 GROUP BY POSITION('m',LCASE("first_name")) ORDER BY POSITION('m',LCASE("first_name")) DESC;
POSITION(m,LCASE(first_name)):i| pos:l
-------------------------------+---------------
9 |1
7 |1
4 |3
3 |6
2 |1
1 |9
;
selectInsertWithPositionAndCondition
SELECT INSERT("first_name",POSITION('m',"first_name"),1,'M') modified, POSITION('m',"first_name") pos FROM "test_emp" WHERE POSITION('m',"first_name") > 1;
modified:s | pos:i
---------------+---------------
SuMant |3
RaMzi |3
PrasadraM |9
DoMenick |3
OtMar |3
AdaMantios |4
HidefuMi |7
MayuMi |5
PreMal |4
SoMnath |3
AMabile |2
AruMugam |4
ReMzi |3
;
selectLocateAndInsertWithLocateWithConditionAndThreeParameters
SELECT LOCATE('a',"first_name",7) pos, INSERT("first_name",LOCATE('a',"first_name",7),1,'AAA') FROM "test_emp" WHERE LOCATE('a',"first_name",7) > 0;
pos:i |INSERT(first_name,LOCATE(a,first_name,7),1,AAA):s
---------------+-----------------------------------------------
8 |ChirstiAAAn
7 |DuangkAAAew
8 |PrasadrAAAm
7 |YongqiAAAo
7 |YinghuAAA
8 |BreanndAAA
9 |MargaretAAA
8 |SudharsAAAn
7 |SailajAAA
7 |ArumugAAAm
11 |SreekrishnAAA
;
selectLocateAndInsertWithLocateWithConditionAndTwoParameters
SELECT LOCATE('a',"first_name") pos, INSERT("first_name",LOCATE('a',"first_name"),1,'AAA') FROM "test_emp" WHERE LOCATE('a',"first_name") > 0 ORDER BY "first_name" LIMIT 10;
pos:i |INSERT(first_name,LOCATE(a,first_name),1,AAA):s
---------------+---------------------------------------------
3 |AdAAAmantios
3 |AlAAAin
5 |AlejAAAndro
5 |AlejAAAndro
3 |AmAAAbile
7 |ArumugAAAm
2 |BAAAder
2 |BAAAsil
5 |BerhAAArd
4 |BezAAAlel
;
selectLeft
SELECT LEFT("first_name",2) f FROM "test_emp" ORDER BY "first_name" LIMIT 10;
f:s
Ad
Al
Al
Al
Am
An
An
Ar
Ar
Ba
;
selectRight
SELECT RIGHT("first_name",2) f FROM "test_emp" ORDER BY "first_name" LIMIT 10;
f:s
os
in
ro
ro
le
ke
sh
if
am
er
;
selectRightWithGroupByAndOrderBy
SELECT RIGHT("first_name",2) f, COUNT(*) count FROM "test_emp" GROUP BY RIGHT("first_name",2) ORDER BY RIGHT("first_name",2) LIMIT 10;
f:s |count:l
---------------+---------------------------------------------
af |1
al |2
am |2
an |7
ao |3
ar |2
ay |1
be |1
bu |1
by |1
;
upperCasingTheSecondLetterFromTheRightFromFirstName
SELECT CONCAT(CONCAT(SUBSTRING("first_name",1,LENGTH("first_name")-2),UCASE(LEFT(RIGHT("first_name",2),1))),RIGHT("first_name",1)) f FROM "test_emp" ORDER BY "first_name" LIMIT 10;
f:s
AdamantiOs
AlaIn
AlejandRo
AlejandRo
AmabiLe
AnneKe
AnooSh
ArIf
ArumugAm
BadEr
;
upperCasingTheSecondLetterFromTheRightFromFirstNameWithOrderByAndGroupBy
SELECT CONCAT(CONCAT(SUBSTRING("first_name",1,LENGTH("first_name")-2),UCASE(LEFT(RIGHT("first_name",2),1))),RIGHT("first_name",1)) f, COUNT(*) c FROM "test_emp" GROUP BY CONCAT(CONCAT(SUBSTRING("first_name",1,LENGTH("first_name")-2),UCASE(LEFT(RIGHT("first_name",2),1))),RIGHT("first_name",1)) ORDER BY CONCAT(CONCAT(SUBSTRING("first_name",1,LENGTH("first_name")-2),UCASE(LEFT(RIGHT("first_name",2),1))),RIGHT("first_name",1)) LIMIT 10;
f:s |c:l
---------------+---------------------------------------------
AdamantiOs |1
AlaIn |1
AlejandRo |2
AmabiLe |1
AnneKe |1
AnooSh |1
ArIf |1
ArumugAm |1
BadEr |1
BasIl |1
;
upperCasingTheSecondLetterFromTheRightFromFirstNameWithWhere
SELECT CONCAT(CONCAT(SUBSTRING("first_name",1,LENGTH("first_name")-2),UCASE(LEFT(RIGHT("first_name",2),1))),RIGHT("first_name",1)) f, COUNT(*) c FROM "test_emp" WHERE CONCAT(CONCAT(SUBSTRING("first_name",1,LENGTH("first_name")-2),UCASE(LEFT(RIGHT("first_name",2),1))),RIGHT("first_name",1))='AlejandRo' GROUP BY CONCAT(CONCAT(SUBSTRING("first_name",1,LENGTH("first_name")-2),UCASE(LEFT(RIGHT("first_name",2),1))),RIGHT("first_name",1)) ORDER BY CONCAT(CONCAT(SUBSTRING("first_name",1,LENGTH("first_name")-2),UCASE(LEFT(RIGHT("first_name",2),1))),RIGHT("first_name",1)) LIMIT 10;
f:s |c:l
---------------+---------------------------------------------
AlejandRo |2
;

View File

@ -74,3 +74,78 @@ SELECT SPACE("languages") s, COUNT(*) count FROM "test_emp" GROUP BY SPACE("lang
spaceGroupByAndOrderByWithCharLength
SELECT SPACE("languages") s, COUNT(*) count, CAST(CHAR_LENGTH(SPACE("languages")) AS INT) cls FROM "test_emp" WHERE "languages" IS NOT NULL GROUP BY SPACE("languages") ORDER BY SPACE("languages");
selectConcatWithOrderBy
SELECT first_name f, last_name l, CONCAT(first_name,last_name) cct FROM test_emp ORDER BY CONCAT(first_name,last_name) LIMIT 10;
selectNestedConcatWithOrderBy
SELECT first_name f, last_name l, CONCAT(first_name,CONCAT(' ',last_name)) cct FROM test_emp ORDER BY CONCAT(first_name,CONCAT(' ',last_name)) LIMIT 10;
selectConcatWithGroupBy
SELECT CONCAT(first_name,last_name) cct FROM test_emp GROUP BY CONCAT(first_name,last_name) ORDER BY CONCAT(first_name,last_name) LIMIT 1;
selectAsciiOfConcatWithGroupByOrderByCount
SELECT ASCII(CONCAT("first_name","last_name")) ascii, COUNT(*) count FROM "test_emp" GROUP BY ASCII(CONCAT("first_name","last_name")) ORDER BY ASCII(CONCAT("first_name","last_name")) DESC LIMIT 10;
selectRepeatTwice
SELECT "first_name" orig, REPEAT("first_name",2) reps FROM "test_emp" WHERE ASCII("first_name")=65 ORDER BY REPEAT("first_name",2) ASC LIMIT 10;
selectInsertWithLcase
SELECT "first_name" orig, INSERT("first_name",2,1000,LCASE("first_name")) modified FROM "test_emp" WHERE ASCII("first_name")=65 ORDER BY "first_name" ASC LIMIT 10;
selectInsertWithLcaseAndLengthWithOrderBy
SELECT "first_name" origFN, "last_name" origLN, INSERT(UCASE("first_name"),LENGTH("first_name")+1,123,LCASE("last_name")) modified FROM "test_emp" WHERE ASCII("first_name")=65 ORDER BY "first_name" ASC, "last_name" ASC LIMIT 10;
selectInsertWithUcaseWithGroupByAndOrderBy
SELECT INSERT(UCASE("first_name"),2,123000,INSERT(UCASE("last_name"),2,500,' ')) modified, COUNT(*) count FROM "test_emp" WHERE ASCII("first_name")=65 GROUP BY INSERT(UCASE("first_name"),2,123000,INSERT(UCASE("last_name"),2,500,' ')) ORDER BY INSERT(UCASE("first_name"),2,123000,INSERT(UCASE("last_name"),2,500,' ')) ASC LIMIT 10;
selectSubstringWithGroupBy
SELECT SUBSTRING("first_name",1,2) modified, COUNT(*) count FROM "test_emp" WHERE ASCII("first_name")=65 GROUP BY SUBSTRING("first_name",1,2) ORDER BY SUBSTRING("first_name",1,2) ASC LIMIT 10;
selectSubstringWithWhereCountAndGroupBy
SELECT SUBSTRING("first_name",1,2) modified, COUNT(*) count FROM "test_emp" WHERE SUBSTRING("first_name",1,2)='Al' GROUP BY SUBSTRING("first_name",1,2) LIMIT 10;
selectSubstringWithWhereAndGroupBy
SELECT SUBSTRING("first_name",5,20) modified, COUNT(*) count FROM "test_emp" GROUP BY SUBSTRING("first_name",5,20) ORDER BY SUBSTRING("first_name",5,20) LIMIT 10;
selectReplace
SELECT REPLACE("first_name",'A','X') modified, "first_name" origFN FROM "test_emp" ORDER BY "first_name" LIMIT 10;
selectReplaceWithGroupBy
SELECT REPLACE("first_name",'jan','_JAN_') modified, COUNT(*) count FROM "test_emp" GROUP BY REPLACE("first_name",'jan','_JAN_') ORDER BY REPLACE("first_name",'jan','_JAN_') LIMIT 10;
selectReplaceWithCastAndCondition
SELECT REPLACE(CAST("languages" AS VARCHAR),'1','100') foo, "languages" FROM "test_emp" WHERE "languages"=1 OR "languages"=2 LIMIT 5;
selectPositionWithConditionAndLcase
SELECT POSITION('x',LCASE("first_name")) pos, "first_name" FROM "test_emp" WHERE POSITION('x',LCASE("first_name")) != 0;
selectPositionWithLcaseAndConditionWithGroupByAndOrderBy
SELECT POSITION('m',LCASE("first_name")) posOfM, COUNT(*) pos FROM "test_emp" WHERE POSITION('m',LCASE("first_name")) != 0 GROUP BY POSITION('m',LCASE("first_name")) ORDER BY POSITION('m',LCASE("first_name")) DESC;
selectInsertWithPositionAndCondition
SELECT INSERT("first_name",POSITION('m',"first_name"),1,'M') modified, POSITION('m',"first_name") pos FROM "test_emp" WHERE POSITION('m',"first_name") > 1;
selectLocateAndInsertWithLocateWithConditionAndThreeParameters
SELECT LOCATE('a',"first_name",7) pos, INSERT("first_name",LOCATE('a',"first_name",7),1,'AAA') inserted FROM "test_emp" WHERE LOCATE('a',"first_name",7) > 0;
selectLocateAndInsertWithLocateWithConditionAndTwoParameters
SELECT LOCATE('a',"first_name") pos, INSERT("first_name",LOCATE('a',"first_name"),1,'AAA') inserted FROM "test_emp" WHERE LOCATE('a',"first_name") > 0 ORDER BY "first_name" LIMIT 10;
selectLeft
SELECT LEFT("first_name",2) f FROM "test_emp" ORDER BY "first_name" LIMIT 10;
selectRight
SELECT RIGHT("first_name",2) f FROM "test_emp" ORDER BY "first_name" LIMIT 10;
selectRightWithGroupByAndOrderBy
SELECT RIGHT("first_name",2) f, COUNT(*) count FROM "test_emp" GROUP BY RIGHT("first_name",2) ORDER BY RIGHT("first_name",2) LIMIT 10;
upperCasingTheSecondLetterFromTheRightFromFirstName
SELECT CONCAT(CONCAT(SUBSTRING("first_name",1,LENGTH("first_name")-2),UCASE(LEFT(RIGHT("first_name",2),1))),RIGHT("first_name",1)) f FROM "test_emp" ORDER BY "first_name" LIMIT 10;
upperCasingTheSecondLetterFromTheRightFromFirstNameWithOrderByAndGroupBy
SELECT CONCAT(CONCAT(SUBSTRING("first_name",1,LENGTH("first_name")-2),UCASE(LEFT(RIGHT("first_name",2),1))),RIGHT("first_name",1)) f, COUNT(*) c FROM "test_emp" GROUP BY CONCAT(CONCAT(SUBSTRING("first_name",1,LENGTH("first_name")-2),UCASE(LEFT(RIGHT("first_name",2),1))),RIGHT("first_name",1)) ORDER BY CONCAT(CONCAT(SUBSTRING("first_name",1,LENGTH("first_name")-2),UCASE(LEFT(RIGHT("first_name",2),1))),RIGHT("first_name",1)) LIMIT 10;
upperCasingTheSecondLetterFromTheRightFromFirstNameWithWhere
SELECT CONCAT(CONCAT(SUBSTRING("first_name",1,LENGTH("first_name")-2),UCASE(LEFT(RIGHT("first_name",2),1))),RIGHT("first_name",1)) f, COUNT(*) c FROM "test_emp" WHERE CONCAT(CONCAT(SUBSTRING("first_name",1,LENGTH("first_name")-2),UCASE(LEFT(RIGHT("first_name",2),1))),RIGHT("first_name",1))='AlejandRo' GROUP BY CONCAT(CONCAT(SUBSTRING("first_name",1,LENGTH("first_name")-2),UCASE(LEFT(RIGHT("first_name",2),1))),RIGHT("first_name",1)) ORDER BY CONCAT(CONCAT(SUBSTRING("first_name",1,LENGTH("first_name")-2),UCASE(LEFT(RIGHT("first_name",2),1))),RIGHT("first_name",1)) LIMIT 10;