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:
parent
2d87287c0d
commit
4c388539a0
|
@ -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)));
|
||||
|
@ -338,6 +356,47 @@ public class FunctionRegistry {
|
|||
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
|
||||
return StringUtils.camelCaseToUnderscore(name);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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 {
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
@ -145,6 +145,8 @@ public final class CsvTestUtils {
|
|||
return "double";
|
||||
case "ts":
|
||||
return "timestamp";
|
||||
case "bt":
|
||||
return "byte";
|
||||
default:
|
||||
return type;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
;
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue