diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rules/FoldConstantRuleOnFE.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rules/FoldConstantRuleOnFE.java index fdd3b02e6fd483..85002373f81812 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rules/FoldConstantRuleOnFE.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/expression/rules/FoldConstantRuleOnFE.java @@ -91,6 +91,7 @@ import com.google.common.collect.Lists; import org.apache.commons.codec.digest.DigestUtils; +import java.time.DateTimeException; import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -435,6 +436,8 @@ public Expression visitCast(Cast cast, ExpressionRewriteContext context) { // If cast is from type coercion, we don't use NULL literal and will throw exception. throw t; } + } catch (DateTimeException e) { + return new NullLiteral(dataType); } } try { diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/ExecFunction.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/ExecFunction.java index 6778c0971edcbd..126449f4b04e34 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/ExecFunction.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/ExecFunction.java @@ -43,4 +43,9 @@ * return type */ String returnType(); + + /** + * hasVarArgsc + */ + boolean varArgs() default false; } diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/ExpressionEvaluator.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/ExpressionEvaluator.java index 566798ec2d4e46..f3d471b2abedc4 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/ExpressionEvaluator.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/ExpressionEvaluator.java @@ -18,6 +18,7 @@ package org.apache.doris.nereids.trees.expressions; import org.apache.doris.catalog.Env; +import org.apache.doris.catalog.Type; import org.apache.doris.common.AnalysisException; import org.apache.doris.nereids.trees.expressions.functions.BoundFunction; import org.apache.doris.nereids.trees.expressions.functions.agg.AggregateFunction; @@ -26,6 +27,7 @@ import org.apache.doris.nereids.trees.expressions.functions.executable.DateTimeExtractAndTransform; import org.apache.doris.nereids.trees.expressions.functions.executable.ExecutableFunctions; import org.apache.doris.nereids.trees.expressions.functions.executable.NumericArithmetic; +import org.apache.doris.nereids.trees.expressions.functions.executable.StringArithmetic; import org.apache.doris.nereids.trees.expressions.functions.executable.TimeRoundSeries; import org.apache.doris.nereids.trees.expressions.literal.DateLiteral; import org.apache.doris.nereids.trees.expressions.literal.Literal; @@ -36,6 +38,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMultimap; +import java.lang.reflect.Array; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; @@ -96,10 +99,30 @@ public Expression eval(Expression expression) { } private Expression invoke(Expression expression, String fnName, DataType[] args) { - FunctionSignature signature = new FunctionSignature(fnName, args, null); + FunctionSignature signature = new FunctionSignature(fnName, args, null, false); FunctionInvoker invoker = getFunction(signature); if (invoker != null) { try { + if (invoker.getSignature().hasVarArgs()) { + int fixedArgsSize = invoker.getSignature().getArgTypes().length - 1; + int totalSize = expression.children().size(); + Class[] parameterTypes = invoker.getMethod().getParameterTypes(); + Class parameterType = parameterTypes[parameterTypes.length - 1]; + Class componentType = parameterType.getComponentType(); + Object varArgs = Array.newInstance(componentType, totalSize - fixedArgsSize); + for (int i = fixedArgsSize; i < totalSize; i++) { + if (!(expression.children().get(i) instanceof NullLiteral)) { + Array.set(varArgs, i - fixedArgsSize, expression.children().get(i)); + } + } + Object[] objects = new Object[fixedArgsSize + 1]; + for (int i = 0; i < fixedArgsSize; i++) { + objects[i] = expression.children().get(i); + } + objects[fixedArgsSize] = varArgs; + + return invoker.invokeVars(objects); + } return invoker.invoke(expression.children()); } catch (AnalysisException e) { return expression; @@ -114,9 +137,34 @@ private FunctionInvoker getFunction(FunctionSignature signature) { DataType[] candidateTypes = candidate.getSignature().getArgTypes(); DataType[] expectedTypes = signature.getArgTypes(); + if (candidate.getSignature().hasVarArgs()) { + if (candidateTypes.length > expectedTypes.length) { + continue; + } + boolean match = true; + for (int i = 0; i < candidateTypes.length - 1; i++) { + if (!(expectedTypes[i].toCatalogDataType().matchesType(candidateTypes[i].toCatalogDataType()))) { + match = false; + break; + } + } + Type varType = candidateTypes[candidateTypes.length - 1].toCatalogDataType(); + for (int i = candidateTypes.length - 1; i < expectedTypes.length; i++) { + if (!(expectedTypes[i].toCatalogDataType().matchesType(varType))) { + match = false; + break; + } + } + if (match) { + return candidate; + } else { + continue; + } + } if (candidateTypes.length != expectedTypes.length) { continue; } + boolean match = true; for (int i = 0; i < candidateTypes.length; i++) { if (!(expectedTypes[i].toCatalogDataType().matchesType(candidateTypes[i].toCatalogDataType()))) { @@ -143,6 +191,7 @@ private void registerFunctions() { DateLiteral.class, DateTimeArithmetic.class, NumericArithmetic.class, + StringArithmetic.class, TimeRoundSeries.class ); for (Class cls : classes) { @@ -172,7 +221,7 @@ private void registerFEFunction(ImmutableMultimap.Builder args) throws AnalysisException { throw new AnalysisException(e.getLocalizedMessage()); } } + + public Literal invokeVars(Object[] args) throws AnalysisException { + try { + return (Literal) method.invoke(null, args); + } catch (InvocationTargetException | IllegalAccessException | IllegalArgumentException e) { + throw new AnalysisException(e.getLocalizedMessage()); + } + } } /** @@ -213,11 +270,13 @@ public static class FunctionSignature { private final String name; private final DataType[] argTypes; private final DataType returnType; + private final boolean hasVarArgs; - public FunctionSignature(String name, DataType[] argTypes, DataType returnType) { + public FunctionSignature(String name, DataType[] argTypes, DataType returnType, boolean hasVarArgs) { this.name = name; this.argTypes = argTypes; this.returnType = returnType; + this.hasVarArgs = hasVarArgs; } public DataType[] getArgTypes() { @@ -231,6 +290,10 @@ public DataType getReturnType() { public String getName() { return name; } + + public boolean hasVarArgs() { + return hasVarArgs; + } } } diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/executable/StringArithmetic.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/executable/StringArithmetic.java new file mode 100644 index 00000000000000..6f2ff11ad9a139 --- /dev/null +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/executable/StringArithmetic.java @@ -0,0 +1,856 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.doris.nereids.trees.expressions.functions.executable; + +import org.apache.doris.nereids.exceptions.AnalysisException; +import org.apache.doris.nereids.trees.expressions.ExecFunction; +import org.apache.doris.nereids.trees.expressions.Expression; +import org.apache.doris.nereids.trees.expressions.literal.ArrayLiteral; +import org.apache.doris.nereids.trees.expressions.literal.BigIntLiteral; +import org.apache.doris.nereids.trees.expressions.literal.BooleanLiteral; +import org.apache.doris.nereids.trees.expressions.literal.DateTimeLiteral; +import org.apache.doris.nereids.trees.expressions.literal.DateTimeV2Literal; +import org.apache.doris.nereids.trees.expressions.literal.DecimalLiteral; +import org.apache.doris.nereids.trees.expressions.literal.DecimalV3Literal; +import org.apache.doris.nereids.trees.expressions.literal.DoubleLiteral; +import org.apache.doris.nereids.trees.expressions.literal.FloatLiteral; +import org.apache.doris.nereids.trees.expressions.literal.IntegerLiteral; +import org.apache.doris.nereids.trees.expressions.literal.LargeIntLiteral; +import org.apache.doris.nereids.trees.expressions.literal.Literal; +import org.apache.doris.nereids.trees.expressions.literal.NullLiteral; +import org.apache.doris.nereids.trees.expressions.literal.SmallIntLiteral; +import org.apache.doris.nereids.trees.expressions.literal.StringLikeLiteral; +import org.apache.doris.nereids.trees.expressions.literal.StringLiteral; +import org.apache.doris.nereids.trees.expressions.literal.TinyIntLiteral; +import org.apache.doris.nereids.trees.expressions.literal.VarcharLiteral; + +import java.io.UnsupportedEncodingException; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.List; + +/** + * executable functions: + * concat + */ +public class StringArithmetic { + private static Expression castStringLikeLiteral(StringLikeLiteral first, String value) { + if (first instanceof StringLiteral) { + return new StringLiteral(value); + } else if (first instanceof VarcharLiteral) { + return new VarcharLiteral(value); + } + throw new AnalysisException("Unsupported string literal type: " + first.getClass().getSimpleName()); + } + + /** + * Executable arithmetic functions concat + */ + @ExecFunction(name = "concat", argTypes = {"VARCHAR", "VARCHAR"}, returnType = "VARCHAR") + public static Expression concatVarcharVarchar(StringLikeLiteral first, StringLikeLiteral second) { + String result = first.getValue() + second.getValue(); + return castStringLikeLiteral(first, result); + } + + private static String substringImpl(String first, int second, int third) { + int stringLength = first.length(); + if (stringLength == 0) { + return ""; + } + int leftIndex = 0; + if (second < (- stringLength)) { + return ""; + } else if (second < 0) { + leftIndex = stringLength + second; + } else if (second <= stringLength) { + leftIndex = second - 1; + } else { + return ""; + } + int rightIndex = 0; + if (third <= 0) { + return ""; + } else if ((third + leftIndex) > stringLength) { + rightIndex = stringLength; + } else { + rightIndex = third + leftIndex; + } + return first.substring(leftIndex, rightIndex); + } + + /** + * Executable arithmetic functions substring + */ + @ExecFunction(name = "substring", argTypes = {"VARCHAR", "INT", "INT"}, returnType = "VARCHAR") + public static Expression substringVarcharIntInt(StringLikeLiteral first, + IntegerLiteral second, IntegerLiteral third) { + return castStringLikeLiteral(first, substringImpl(first.getValue(), second.getValue(), third.getValue())); + } + + /** + * Executable arithmetic functions length + */ + @ExecFunction(name = "length", argTypes = {"VARCHAR"}, returnType = "INT") + public static Expression lengthVarchar(StringLikeLiteral first) { + return new IntegerLiteral(first.getValue().length()); + } + + /** + * Executable arithmetic functions Lower + */ + @ExecFunction(name = "lower", argTypes = {"VARCHAR"}, returnType = "VARCHAR") + public static Expression lowerVarchar(StringLikeLiteral first) { + return castStringLikeLiteral(first, first.getValue().toLowerCase()); + } + + /** + * Executable arithmetic functions Upper + */ + @ExecFunction(name = "upper", argTypes = {"VARCHAR"}, returnType = "VARCHAR") + public static Expression upperVarchar(StringLikeLiteral first) { + return castStringLikeLiteral(first, first.getValue().toUpperCase()); + } + + private static String trimImpl(String first, String second, boolean left, boolean right) { + String result = first; + String afterReplace = first; + if (left) { + do { + result = afterReplace; + afterReplace = result.replaceFirst("^" + second, ""); + } while (!afterReplace.equals(result)); + } + if (right) { + do { + result = afterReplace; + afterReplace = result.replaceFirst(second + "$", ""); + } while (!afterReplace.equals(result)); + } + return result; + } + + /** + * Executable arithmetic functions Trim + */ + @ExecFunction(name = "trim", argTypes = {"VARCHAR"}, returnType = "VARCHAR") + public static Expression trimVarchar(StringLikeLiteral first) { + return castStringLikeLiteral(first, trimImpl(first.getValue(), " ", true, true)); + } + + /** + * Executable arithmetic functions Trim + */ + @ExecFunction(name = "trim", argTypes = {"VARCHAR", "VARCHAR"}, returnType = "VARCHAR") + public static Expression trimVarcharVarchar(StringLikeLiteral first, StringLikeLiteral second) { + return castStringLikeLiteral(first, trimImpl(first.getValue(), second.getValue(), true, true)); + } + + /** + * Executable arithmetic functions ltrim + */ + @ExecFunction(name = "ltrim", argTypes = {"VARCHAR"}, returnType = "VARCHAR") + public static Expression ltrimVarchar(StringLikeLiteral first) { + return castStringLikeLiteral(first, trimImpl(first.getValue(), " ", true, false)); + } + + /** + * Executable arithmetic functions ltrim + */ + @ExecFunction(name = "ltrim", argTypes = {"VARCHAR", "VARCHAR"}, returnType = "VARCHAR") + public static Expression ltrimVarcharVarchar(StringLikeLiteral first, StringLikeLiteral second) { + return castStringLikeLiteral(first, trimImpl(first.getValue(), second.getValue(), true, false)); + } + + /** + * Executable arithmetic functions rtrim + */ + @ExecFunction(name = "rtrim", argTypes = {"VARCHAR"}, returnType = "VARCHAR") + public static Expression rtrimVarchar(StringLikeLiteral first) { + return castStringLikeLiteral(first, trimImpl(first.getValue(), " ", false, true)); + } + + /** + * Executable arithmetic functions rtrim + */ + @ExecFunction(name = "rtrim", argTypes = {"VARCHAR", "VARCHAR"}, returnType = "VARCHAR") + public static Expression rtrimVarcharVarchar(StringLikeLiteral first, StringLikeLiteral second) { + return castStringLikeLiteral(first, trimImpl(first.getValue(), second.getValue(), false, true)); + } + + /** + * Executable arithmetic functions Replace + */ + @ExecFunction(name = "replace", argTypes = {"VARCHAR", "VARCHAR", "VARCHAR"}, returnType = "VARCHAR") + public static Expression replace(StringLikeLiteral first, StringLikeLiteral second, StringLikeLiteral third) { + if (second.getValue().length() == 0) { + return castStringLikeLiteral(first, first.getValue()); + } + return castStringLikeLiteral(first, first.getValue().replace(second.getValue(), third.getValue())); + } + + /** + * Executable arithmetic functions Left + */ + @ExecFunction(name = "left", argTypes = {"VARCHAR", "INT"}, returnType = "VARCHAR") + public static Expression left(StringLikeLiteral first, IntegerLiteral second) { + if (second.getValue() <= 0) { + return castStringLikeLiteral(first, ""); + } else if (second.getValue() < first.getValue().length()) { + return castStringLikeLiteral(first, first.getValue().substring(0, second.getValue())); + } else { + return first; + } + } + + /** + * Executable arithmetic functions Right + */ + @ExecFunction(name = "right", argTypes = {"VARCHAR", "INT"}, returnType = "VARCHAR") + public static Expression right(StringLikeLiteral first, IntegerLiteral second) { + if (second.getValue() < (- first.getValue().length()) || Math.abs(second.getValue()) == 0) { + return castStringLikeLiteral(first, ""); + } else if (second.getValue() > first.getValue().length()) { + return first; + } else { + if (second.getValue() > 0) { + return castStringLikeLiteral(first, first.getValue().substring( + first.getValue().length() - second.getValue(), first.getValue().length())); + } else { + return castStringLikeLiteral(first, first.getValue().substring( + Math.abs(second.getValue()) - 1, first.getValue().length())); + } + } + } + + /** + * Executable arithmetic functions Locate + */ + @ExecFunction(name = "locate", argTypes = {"VARCHAR", "VARCHAR"}, returnType = "INT") + public static Expression locate(StringLikeLiteral first, StringLikeLiteral second) { + return new IntegerLiteral(second.getValue().trim().indexOf(first.getValue()) + 1); + } + + /** + * Executable arithmetic functions Instr + */ + @ExecFunction(name = "instr", argTypes = {"VARCHAR", "VARCHAR"}, returnType = "INT") + public static Expression instr(StringLikeLiteral first, StringLikeLiteral second) { + return new IntegerLiteral(first.getValue().indexOf(second.getValue()) + 1); + } + + /** + * Executable arithmetic functions Ascii + */ + @ExecFunction(name = "ascii", argTypes = {"VARCHAR"}, returnType = "INT") + public static Expression ascii(StringLikeLiteral first) { + if (first.getValue().length() == 0) { + return new IntegerLiteral(0); + } + char firstChar = first.getValue().charAt(0); + return new IntegerLiteral(firstChar); + } + + /** + * Executable arithmetic functions Bin + */ + @ExecFunction(name = "bin", argTypes = {"BIGINT"}, returnType = "VARCHAR") + public static Expression bin(BigIntLiteral first) { + return new VarcharLiteral(Long.toBinaryString(first.getValue())); + } + + /** + * Executable arithmetic functions ConcatWs + */ + @ExecFunction(name = "concat_ws", argTypes = {"VARCHAR", "ARRAY"}, returnType = "VARCHAR") + public static Expression concatWsVarcharArray(StringLikeLiteral first, ArrayLiteral second) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < second.getValue().size() - 1; i++) { + if (!(second.getValue().get(i) instanceof NullLiteral)) { + sb.append(second.getValue().get(i).getValue()); + sb.append(first.getValue()); + } + } + sb.append(second.getValue().get(second.getValue().size() - 1).getValue()); + return castStringLikeLiteral(first, sb.toString()); + } + + /** + * Executable arithmetic functions ConcatWs + */ + @ExecFunction(varArgs = true, name = "concat_ws", argTypes = {"VARCHAR", "VARCHAR"}, returnType = "VARCHAR") + public static Expression concatWsVarcharVarchar(StringLikeLiteral first, VarcharLiteral... second) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < second.length - 1; i++) { + sb.append(second[i].getValue()); + sb.append(first.getValue()); + } + sb.append(second[second.length - 1].getValue()); + return castStringLikeLiteral(first, sb.toString()); + } + + /** + * Executable arithmetic functions CharacterLength + */ + @ExecFunction(name = "character_length", argTypes = {"VARCHAR"}, returnType = "INT") + public static Expression characterLength(StringLikeLiteral first) { + return new IntegerLiteral(first.getValue().length()); + } + + private static boolean isSeparator(char c) { + if (".$|()[{^?*+\\".indexOf(c) == -1) { + return false; + } else { + return true; + } + } + + /** + * Executable arithmetic functions initCap + */ + @ExecFunction(name = "initcap", argTypes = {"VARCHAR"}, returnType = "VARCHAR") + public static Expression initCap(StringLikeLiteral first) { + StringBuilder result = new StringBuilder(first.getValue().length()); + boolean capitalizeNext = true; + + for (char c : first.getValue().toCharArray()) { + if (Character.isWhitespace(c) || isSeparator(c)) { + result.append(c); + capitalizeNext = true; // Next character should be capitalized + } else if (capitalizeNext) { + result.append(Character.toUpperCase(c)); + capitalizeNext = false; + } else { + result.append(Character.toLowerCase(c)); + } + } + return castStringLikeLiteral(first, result.toString()); + } + + /** + * Executable arithmetic functions md5 + */ + @ExecFunction(name = "md5", argTypes = {"VARCHAR"}, returnType = "VARCHAR") + public static Expression md5(StringLikeLiteral first) { + try { + MessageDigest md = MessageDigest.getInstance("MD5"); + // Update the digest with the input bytes + md.update(first.getValue().getBytes()); + return castStringLikeLiteral(first, bytesToHex(md.digest())); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + + /** + * Executable arithmetic functions md5 + */ + @ExecFunction(varArgs = true, name = "md5sum", argTypes = {"VARCHAR"}, returnType = "VARCHAR") + public static Expression md5Sum(VarcharLiteral... first) { + try { + // Step 1: Create a MessageDigest instance for MD5 + MessageDigest md = MessageDigest.getInstance("MD5"); + + // Step 2: Concatenate all strings in the list into one string + StringBuilder combinedInput = new StringBuilder(); + for (StringLikeLiteral input : first) { + combinedInput.append(input.getValue()); + } + + // Step 3: Convert the combined string to a byte array and pass it to the digest() method + byte[] messageDigest = md.digest(combinedInput.toString().getBytes()); + + // Step 4: Convert the byte array into a hexadecimal string + StringBuilder hexString = new StringBuilder(); + for (byte b : messageDigest) { + String hex = Integer.toHexString(0xff & b); + if (hex.length() == 1) { + hexString.append('0'); // Add leading zero if needed + } + hexString.append(hex); + } + + // Step 5: Return the hexadecimal string + return castStringLikeLiteral(first[0], hexString.toString()); + + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + + // Helper method to convert a byte array to a hexadecimal string + private static String bytesToHex(byte[] bytes) { + StringBuilder sb = new StringBuilder(); + for (byte b : bytes) { + sb.append(String.format("%02x", b)); + } + return sb.toString(); + } + + private static int compareLiteral(Literal first, Literal... second) { + for (int i = 0; i < second.length; i++) { + if (second[i].getValue().equals(first.getValue())) { + return i + 1; + } + } + return 0; + } + + /** + * Executable arithmetic functions field + */ + @ExecFunction(varArgs = true, name = "field", argTypes = {"INT", "INT"}, returnType = "INT") + public static Expression fieldInt(IntegerLiteral first, IntegerLiteral... second) { + return new IntegerLiteral(compareLiteral(first, second)); + } + + /** + * Executable arithmetic functions field + */ + @ExecFunction(varArgs = true, name = "field", argTypes = {"TINYINT", "TINYINT"}, returnType = "INT") + public static Expression fieldTinyInt(TinyIntLiteral first, TinyIntLiteral... second) { + return new IntegerLiteral(compareLiteral(first, second)); + } + + /** + * Executable arithmetic functions field + */ + @ExecFunction(varArgs = true, name = "field", argTypes = {"SMALLINT", "SMALLINT"}, returnType = "INT") + public static Expression fieldSmallInt(SmallIntLiteral first, SmallIntLiteral... second) { + return new IntegerLiteral(compareLiteral(first, second)); + } + + /** + * Executable arithmetic functions field + */ + @ExecFunction(varArgs = true, name = "field", argTypes = {"BIGINT", "BIGINT"}, returnType = "INT") + public static Expression fieldBigInt(BigIntLiteral first, BigIntLiteral... second) { + return new IntegerLiteral(compareLiteral(first, second)); + } + + /** + * Executable arithmetic functions field + */ + @ExecFunction(varArgs = true, name = "field", argTypes = {"LARGEINT", "LARGEINT"}, returnType = "INT") + public static Expression fieldLargeInt(LargeIntLiteral first, LargeIntLiteral... second) { + return new IntegerLiteral(compareLiteral(first, second)); + } + + /** + * Executable arithmetic functions field + */ + @ExecFunction(varArgs = true, name = "field", argTypes = {"FLOAT", "FLOAT"}, returnType = "INT") + public static Expression fieldFloat(FloatLiteral first, FloatLiteral... second) { + return new IntegerLiteral(compareLiteral(first, second)); + } + + /** + * Executable arithmetic functions field + */ + @ExecFunction(varArgs = true, name = "field", argTypes = {"DOUBLE", "DOUBLE"}, returnType = "INT") + public static Expression fieldDouble(DoubleLiteral first, DoubleLiteral... second) { + return new IntegerLiteral(compareLiteral(first, second)); + } + + /** + * Executable arithmetic functions field + */ + @ExecFunction(varArgs = true, name = "field", argTypes = {"DECIMAL", "DECIMAL"}, returnType = "INT") + public static Expression fieldDecimalV2(DecimalLiteral first, DecimalLiteral... second) { + return new IntegerLiteral(compareLiteral(first, second)); + } + + /** + * Executable arithmetic functions field + */ + @ExecFunction(varArgs = true, name = "field", argTypes = {"DECIMALV3", "DECIMALV3"}, returnType = "INT") + public static Expression fieldDecimalV3(DecimalV3Literal first, DecimalV3Literal... second) { + return new IntegerLiteral(compareLiteral(first, second)); + } + + /** + * Executable arithmetic functions field + */ + @ExecFunction(varArgs = true, name = "field", argTypes = {"DATETIME", "DATETIME"}, returnType = "INT") + public static Expression fieldDateTime(DateTimeLiteral first, DateTimeLiteral... second) { + return new IntegerLiteral(compareLiteral(first, second)); + } + + /** + * Executable arithmetic functions field + */ + @ExecFunction(varArgs = true, name = "field", argTypes = {"DATETIMEV2", "DATETIMEV2"}, returnType = "INT") + public static Expression fieldDateTimeV2(DateTimeV2Literal first, DateTimeV2Literal... second) { + return new IntegerLiteral(compareLiteral(first, second)); + } + + /** + * Executable arithmetic functions field + */ + @ExecFunction(varArgs = true, name = "field", argTypes = {"VARCHAR", "VARCHAR"}, returnType = "INT") + public static Expression fieldVarchar(StringLikeLiteral first, VarcharLiteral... second) { + return new IntegerLiteral(compareLiteral(first, second)); + } + + private static int findStringInSet(String target, String input) { + String[] split = input.split(","); + for (int i = 0; i < split.length; i++) { + if (split[i].equals(target)) { + return i + 1; + } + } + return 0; + } + + /** + * Executable arithmetic functions find_in_set + */ + @ExecFunction(name = "find_in_set", argTypes = {"VARCHAR", "VARCHAR"}, returnType = "INT") + public static Expression findInSetVarchar(StringLikeLiteral first, StringLikeLiteral second) { + return new IntegerLiteral(findStringInSet(first.getValue(), second.getValue())); + } + + /** + * Executable arithmetic functions repeat + */ + @ExecFunction(name = "repeat", argTypes = {"VARCHAR", "INT"}, returnType = "VARCHAR") + public static Expression repeat(StringLikeLiteral first, IntegerLiteral second) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < second.getValue(); i++) { + sb.append(first.getValue()); + } + return castStringLikeLiteral(first, sb.toString()); + } + + /** + * Executable arithmetic functions reverse + */ + @ExecFunction(name = "reverse", argTypes = {"VARCHAR"}, returnType = "VARCHAR") + public static Expression reverseVarchar(StringLikeLiteral first) { + StringBuilder sb = new StringBuilder(); + sb.append(first.getValue()); + return castStringLikeLiteral(first, sb.reverse().toString()); + } + + /** + * Executable arithmetic functions space + */ + @ExecFunction(name = "space", argTypes = {"INT"}, returnType = "VARCHAR") + public static Expression space(IntegerLiteral first) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < first.getValue(); i++) { + sb.append(' '); + } + return new VarcharLiteral(sb.toString()); + } + + /** + * Executable arithmetic functions split_by_char + */ + @ExecFunction(name = "split_by_char", argTypes = {"VARCHAR", "VARCHAR"}, returnType = "ARRAY") + public static Expression splitByChar(StringLikeLiteral first, StringLikeLiteral second) { + String[] result = first.getValue().split(second.getValue()); + List items = new ArrayList<>(); + for (int i = 1; i < result.length; i++) { + items.add((Literal) castStringLikeLiteral(first, result[i])); + } + return new ArrayLiteral(items); + } + + /** + * Executable arithmetic functions split_part + */ + @ExecFunction(name = "split_part", argTypes = {"VARCHAR", "VARCHAR", "INT"}, returnType = "VARCHAR") + public static Expression splitPart(StringLikeLiteral first, StringLikeLiteral chr, IntegerLiteral number) { + if (first.getValue().equals(chr.getValue())) { + if (Math.abs(number.getValue()) == 1 || Math.abs(number.getValue()) == 2) { + return castStringLikeLiteral(first, ""); + } + } + String separator = chr.getValue(); + String[] parts = null; + if (number.getValue() < 0) { + StringBuilder sb = new StringBuilder(first.getValue()); + StringBuilder seperatorBuilder = new StringBuilder(separator); + separator = seperatorBuilder.reverse().toString(); + if (".$|()[{^?*+\\".contains(separator) || separator.startsWith("\\")) { + separator = "\\" + separator; + } + parts = sb.reverse().toString().split(separator); + } else { + if (".$|()[{^?*+\\".contains(separator) || separator.startsWith("\\")) { + separator = "\\" + separator; + } + parts = first.getValue().split(separator); + } + + if (parts.length < Math.abs(number.getValue()) || number.getValue() == 0) { + if (parts.length == Math.abs(number.getValue()) - 1) { + if (number.getValue() < 0 && first.getValue().startsWith(chr.getValue()) + || number.getValue() > 0 && first.getValue().endsWith(chr.getValue())) { + return castStringLikeLiteral(first, ""); + } + } + return new NullLiteral(); + } else if (number.getValue() < 0) { + StringBuilder result = new StringBuilder(parts[Math.abs(number.getValue()) - 1]); + return castStringLikeLiteral(first, result.reverse().toString()); + } else { + return castStringLikeLiteral(first, parts[number.getValue() - 1]); + } + } + + /** + * Executable arithmetic functions substring_index + */ + @ExecFunction(name = "substring_index", argTypes = {"VARCHAR", "VARCHAR", "INT"}, returnType = "VARCHAR") + public static Expression substringIndex(StringLikeLiteral first, StringLikeLiteral chr, IntegerLiteral number) { + String[] parts = first.getValue().split(chr.getValue()); + if (Math.abs(number.getValue()) >= parts.length) { + return first; + } + int leftIndex; + int rightIndex; + if (parts.length < number.getValue() || number.getValue() < (- parts.length) || number.getValue() == 0) { + return castStringLikeLiteral(first, ""); + } else if (number.getValue() < 0) { + leftIndex = parts.length + number.getValue(); + rightIndex = parts.length; + } else { + leftIndex = 0; + rightIndex = number.getValue(); + } + StringBuilder sb = new StringBuilder(); + for (int i = leftIndex; i < rightIndex - 1; i++) { + sb.append(parts[i]); + sb.append(chr.getValue()); + } + sb.append(parts[rightIndex - 1]); + return castStringLikeLiteral(first, sb.toString()); + } + + /** + * Executable arithmetic functions strcmp + */ + @ExecFunction(name = "strcmp", argTypes = {"VARCHAR", "VARCHAR"}, returnType = "TINYINT") + public static Expression strcmp(StringLikeLiteral first, StringLikeLiteral second) { + int result = first.getValue().compareTo(second.getValue()); + if (result == 0) { + return new TinyIntLiteral((byte) 0); + } else if (result < 0) { + return new TinyIntLiteral((byte) -1); + } else { + return new TinyIntLiteral((byte) 1); + } + } + + /** + * Executable arithmetic functions strLeft + */ + @ExecFunction(name = "strleft", argTypes = {"VARCHAR", "INT"}, returnType = "VARCHAR") + public static Expression strLeft(StringLikeLiteral first, IntegerLiteral second) { + if (second.getValue() <= 0) { + return castStringLikeLiteral(first, ""); + } else if (second.getValue() > first.getValue().length()) { + return first; + } else { + return castStringLikeLiteral(first, first.getValue().substring(0, second.getValue())); + } + } + + /** + * Executable arithmetic functions strRight + */ + @ExecFunction(name = "strright", argTypes = {"VARCHAR", "INT"}, returnType = "VARCHAR") + public static Expression strRight(StringLikeLiteral first, IntegerLiteral second) { + if (second.getValue() < (- first.getValue().length()) || Math.abs(second.getValue()) == 0) { + return castStringLikeLiteral(first, ""); + } else if (second.getValue() > first.getValue().length()) { + return first; + } else { + if (second.getValue() > 0) { + return castStringLikeLiteral(first, first.getValue().substring( + first.getValue().length() - second.getValue(), first.getValue().length())); + } else { + return castStringLikeLiteral(first, first.getValue().substring( + Math.abs(second.getValue()) - 1, first.getValue().length())); + } + } + } + + /** + * Executable arithmetic functions overlay + */ + @ExecFunction(name = "overlay", argTypes = {"VARCHAR", "INT", "INT", "VARCHAR"}, returnType = "VARCHAR") + public static Expression overlay(StringLikeLiteral first, + IntegerLiteral second, IntegerLiteral third, StringLikeLiteral four) { + StringBuilder sb = new StringBuilder(); + if (second.getValue() <= 0 || second.getValue() > first.getValue().length()) { + return first; + } else { + if (third.getValue() < 0 || third.getValue() > (first.getValue().length() - third.getValue())) { + sb.append(first.getValue().substring(0, second.getValue() - 1)); + sb.append(four.getValue()); + return castStringLikeLiteral(first, sb.toString()); + } else { + sb.append(first.getValue().substring(0, second.getValue() - 1)); + sb.append(four.getValue()); + sb.append(first.getValue().substring(second.getValue() + + third.getValue() - 1, first.getValue().length())); + return castStringLikeLiteral(first, sb.toString()); + } + } + } + + /** + * Executable arithmetic functions parseurl + */ + @ExecFunction(name = "parse_url", argTypes = {"VARCHAR", "VARCHAR"}, returnType = "VARCHAR") + public static Expression parseurl(StringLikeLiteral first, StringLikeLiteral second) { + URI uri = null; + try { + uri = new URI(first.getValue()); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + StringBuilder sb = new StringBuilder(); + switch (second.getValue().toUpperCase()) { + case "PROTOCOL": + sb.append(uri.getScheme()); // e.g., http, https + break; + case "HOST": + sb.append(uri.getHost()); // e.g., www.example.com + break; + case "PATH": + sb.append(uri.getPath()); // e.g., /page + break; + case "REF": + try { + sb.append(uri.toURL().getRef()); // e.g., /page + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + break; + case "AUTHORITY": + sb.append(uri.getAuthority()); // e.g., param1=value1¶m2=value2 + break; + case "FILE": + try { + sb.append(uri.toURL().getFile()); // e.g., param1=value1¶m2=value2 + } catch (MalformedURLException e) { + throw new RuntimeException(e); + } + break; + case "QUERY": + sb.append(uri.getQuery()); // e.g., param1=value1¶m2=value2 + break; + case "PORT": + sb.append(uri.getPort()); + break; + case "USERINFO": + sb.append(uri.getUserInfo()); // e.g., user:pass + break; + default: + throw new RuntimeException("Valid URL parts are 'PROTOCOL', 'HOST', " + + "'PATH', 'REF', 'AUTHORITY', 'FILE', 'USERINFO', 'PORT' and 'QUERY'"); + } + return castStringLikeLiteral(first, sb.toString()); + } + + /** + * Executable arithmetic functions urldecode + */ + @ExecFunction(name = "url_decode", argTypes = {"VARCHAR"}, returnType = "VARCHAR") + public static Expression urlDecode(StringLikeLiteral first) { + try { + return castStringLikeLiteral(first, URLDecoder.decode(first.getValue(), StandardCharsets.UTF_8.name())); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + + /** + * Executable arithmetic functions append_trailing_char_if_absent + */ + @ExecFunction(name = "append_trailing_char_if_absent", argTypes = {"VARCHAR", "VARCHAR"}, returnType = "VARCHAR") + public static Expression appendTrailingCharIfAbsent(StringLikeLiteral first, StringLikeLiteral second) { + if (first.getValue().endsWith(second.getValue())) { + return first; + } else { + return castStringLikeLiteral(first, first.getValue() + second.getValue()); + } + } + + /** + * Executable arithmetic functions endsWith + */ + @ExecFunction(name = "ends_with", argTypes = {"VARCHAR", "VARCHAR"}, returnType = "BOOLEAN") + public static Expression endsWith(StringLikeLiteral first, StringLikeLiteral second) { + if (first.getValue().endsWith(second.getValue())) { + return BooleanLiteral.TRUE; + } else { + return BooleanLiteral.FALSE; + } + } + + /** + * Executable arithmetic functions extractUrlParameter + */ + @ExecFunction(name = "extract_url_parameter", argTypes = {"VARCHAR", "VARCHAR"}, returnType = "VARCHAR") + public static Expression extractUrlParameter(StringLikeLiteral first, StringLikeLiteral second) { + if (first.getValue() == null || first.getValue().indexOf('?') == -1) { + return castStringLikeLiteral(first, ""); + } + + String[] urlParts = first.getValue().split("\\?"); + if (urlParts.length > 1) { + String query = urlParts[1]; + String[] pairs = query.split("&"); + + for (String pair : pairs) { + String[] keyValue = pair.split("="); + if (second.getValue().equals(keyValue[0])) { + return castStringLikeLiteral(first, keyValue[1]); + } + } + } + return castStringLikeLiteral(first, ""); + } + + /** + * Executable arithmetic functions quote + */ + @ExecFunction(name = "quote", argTypes = {"VARCHAR"}, returnType = "VARCHAR") + public static Expression quote(StringLikeLiteral first) { + return castStringLikeLiteral(first, "\'" + first.getValue() + "\'"); + } + + /** + * Executable arithmetic functions replaceEmpty + */ + @ExecFunction(name = "replace_empty", argTypes = {"VARCHAR", "VARCHAR", "VARCHAR"}, returnType = "VARCHAR") + public static Expression replaceEmpty(StringLikeLiteral first, StringLikeLiteral second, StringLikeLiteral third) { + return castStringLikeLiteral(first, first.getValue().replace(second.getValue(), third.getValue())); + } + +} diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/Field.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/Field.java index ea454178c7cc56..25bee5e40a4dc1 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/Field.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/Field.java @@ -50,19 +50,21 @@ public class Field extends ScalarFunction implements ExplicitlyCastableSignature, PropagateNullable { public static final List SIGNATURES = ImmutableList.of( - FunctionSignature.ret(IntegerType.INSTANCE).varArgs(TinyIntType.INSTANCE), - FunctionSignature.ret(IntegerType.INSTANCE).varArgs(SmallIntType.INSTANCE), - FunctionSignature.ret(IntegerType.INSTANCE).varArgs(IntegerType.INSTANCE), - FunctionSignature.ret(IntegerType.INSTANCE).varArgs(BigIntType.INSTANCE), - FunctionSignature.ret(IntegerType.INSTANCE).varArgs(LargeIntType.INSTANCE), - FunctionSignature.ret(IntegerType.INSTANCE).varArgs(FloatType.INSTANCE), - FunctionSignature.ret(IntegerType.INSTANCE).varArgs(DoubleType.INSTANCE), - FunctionSignature.ret(IntegerType.INSTANCE).varArgs(DecimalV2Type.SYSTEM_DEFAULT), - FunctionSignature.ret(IntegerType.INSTANCE).varArgs(DecimalV3Type.WILDCARD), - FunctionSignature.ret(IntegerType.INSTANCE).varArgs(DateV2Type.INSTANCE), - FunctionSignature.ret(IntegerType.INSTANCE).varArgs(DateTimeV2Type.SYSTEM_DEFAULT), - FunctionSignature.ret(IntegerType.INSTANCE).varArgs(VarcharType.SYSTEM_DEFAULT), - FunctionSignature.ret(IntegerType.INSTANCE).varArgs(StringType.INSTANCE) + FunctionSignature.ret(IntegerType.INSTANCE).varArgs(TinyIntType.INSTANCE, TinyIntType.INSTANCE), + FunctionSignature.ret(IntegerType.INSTANCE).varArgs(SmallIntType.INSTANCE, SmallIntType.INSTANCE), + FunctionSignature.ret(IntegerType.INSTANCE).varArgs(IntegerType.INSTANCE, IntegerType.INSTANCE), + FunctionSignature.ret(IntegerType.INSTANCE).varArgs(BigIntType.INSTANCE, BigIntType.INSTANCE), + FunctionSignature.ret(IntegerType.INSTANCE).varArgs(LargeIntType.INSTANCE, LargeIntType.INSTANCE), + FunctionSignature.ret(IntegerType.INSTANCE).varArgs(FloatType.INSTANCE, FloatType.INSTANCE), + FunctionSignature.ret(IntegerType.INSTANCE).varArgs(DoubleType.INSTANCE, DoubleType.INSTANCE), + FunctionSignature.ret(IntegerType.INSTANCE) + .varArgs(DecimalV2Type.SYSTEM_DEFAULT, DecimalV2Type.SYSTEM_DEFAULT), + FunctionSignature.ret(IntegerType.INSTANCE).varArgs(DecimalV3Type.WILDCARD, DecimalV3Type.WILDCARD), + FunctionSignature.ret(IntegerType.INSTANCE).varArgs(DateV2Type.INSTANCE, DateV2Type.INSTANCE), + FunctionSignature.ret(IntegerType.INSTANCE) + .varArgs(DateTimeV2Type.SYSTEM_DEFAULT, DateTimeV2Type.SYSTEM_DEFAULT), + FunctionSignature.ret(IntegerType.INSTANCE).varArgs(VarcharType.SYSTEM_DEFAULT, VarcharType.SYSTEM_DEFAULT), + FunctionSignature.ret(IntegerType.INSTANCE).varArgs(StringType.INSTANCE, StringType.INSTANCE) ); /** diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/Repeat.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/Repeat.java index b85a812197f55b..5ed3b20ddb465b 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/Repeat.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/scalar/Repeat.java @@ -25,6 +25,7 @@ import org.apache.doris.nereids.trees.expressions.visitor.ExpressionVisitor; import org.apache.doris.nereids.types.IntegerType; import org.apache.doris.nereids.types.StringType; +import org.apache.doris.nereids.types.VarcharType; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; @@ -38,6 +39,7 @@ public class Repeat extends ScalarFunction implements BinaryExpression, ExplicitlyCastableSignature, AlwaysNullable { public static final List SIGNATURES = ImmutableList.of( + FunctionSignature.ret(VarcharType.SYSTEM_DEFAULT).args(VarcharType.SYSTEM_DEFAULT, IntegerType.INSTANCE), FunctionSignature.ret(StringType.INSTANCE).args(StringType.INSTANCE, IntegerType.INSTANCE) ); diff --git a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/ArrayContainsToArrayOverlapTest.java b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/ArrayContainsToArrayOverlapTest.java index 1ef76c14347148..028d85ce097fe6 100644 --- a/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/ArrayContainsToArrayOverlapTest.java +++ b/fe/fe-core/src/test/java/org/apache/doris/nereids/rules/rewrite/ArrayContainsToArrayOverlapTest.java @@ -19,7 +19,6 @@ import org.apache.doris.nereids.rules.expression.ExpressionRewriteTestHelper; import org.apache.doris.nereids.trees.expressions.And; -import org.apache.doris.nereids.trees.expressions.EqualTo; import org.apache.doris.nereids.trees.expressions.Expression; import org.apache.doris.nereids.trees.expressions.Or; import org.apache.doris.nereids.trees.expressions.functions.scalar.ArraysOverlap; @@ -74,9 +73,7 @@ void testAndOther() { .getPlan(); Expression expression = plan.child(0).getExpressions().get(0).child(0); Assertions.assertTrue(expression instanceof Or); - Assertions.assertTrue(expression.child(0) instanceof Or); - Assertions.assertTrue(expression.child(0).child(0) instanceof ArraysOverlap); - Assertions.assertTrue(expression.child(0).child(1) instanceof EqualTo); + Assertions.assertTrue(expression.child(0) instanceof ArraysOverlap); Assertions.assertTrue(expression.child(1) instanceof And); } diff --git a/regression-test/framework/src/main/groovy/org/apache/doris/regression/suite/Suite.groovy b/regression-test/framework/src/main/groovy/org/apache/doris/regression/suite/Suite.groovy index 713a30e98ca076..c7e195224e4ec0 100644 --- a/regression-test/framework/src/main/groovy/org/apache/doris/regression/suite/Suite.groovy +++ b/regression-test/framework/src/main/groovy/org/apache/doris/regression/suite/Suite.groovy @@ -1378,6 +1378,20 @@ class Suite implements GroovyInterceptable { Assert.assertEquals("FINISHED", result) } + void testFoldConst(String foldSql) { + String openFoldConstant = "set debug_skip_fold_constant=false"; + sql(openFoldConstant) + logger.info(foldSql) + List> resultByFoldConstant = sql(foldSql) + logger.info("result by fold constant: " + resultByFoldConstant.toString()) + String closeFoldConstant = "set debug_skip_fold_constant=true"; + sql(closeFoldConstant) + logger.info(foldSql) + List> resultExpected = sql(foldSql) + logger.info("result expected: " + resultExpected.toString()) + Assert.assertEquals(resultExpected, resultByFoldConstant) + } + String getJobName(String dbName, String mtmvName) { String showMTMV = "select JobName from mv_infos('database'='${dbName}') where Name = '${mtmvName}'"; logger.info(showMTMV) diff --git a/regression-test/suites/nereids_p0/expression/fold_constant/fold_constant_by_be.groovy b/regression-test/suites/nereids_p0/expression/fold_constant/fold_constant_by_be.groovy index a8ec9f45def68a..809b8e8b291d23 100644 --- a/regression-test/suites/nereids_p0/expression/fold_constant/fold_constant_by_be.groovy +++ b/regression-test/suites/nereids_p0/expression/fold_constant/fold_constant_by_be.groovy @@ -53,4 +53,4 @@ suite("fold_constant_by_be") { sql 'set query_timeout=12;' qt_sql "select sleep(sign(1)*5);" -} \ No newline at end of file +} diff --git a/regression-test/suites/nereids_p0/expression/fold_constant/fold_constant_string_arithmatic.groovy b/regression-test/suites/nereids_p0/expression/fold_constant/fold_constant_string_arithmatic.groovy new file mode 100644 index 00000000000000..2bcdfc2fd24068 --- /dev/null +++ b/regression-test/suites/nereids_p0/expression/fold_constant/fold_constant_string_arithmatic.groovy @@ -0,0 +1,687 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +suite("fold_constant_string_arithmatic") { + def db = "fold_constant_string_arithmatic" + sql "create database if not exists ${db}" + + sql "set enable_nereids_planner=true" + sql "set enable_fallback_to_original_planner=false" + sql "set enable_fold_constant_by_be=false" + + testFoldConst("SELECT Concat('Hello', ' ', 'World')") + testFoldConst("SELECT Substring('Hello World', 1, 5)") + testFoldConst("SELECT Substring('1', 1, 1)") + testFoldConst("select 100, 'abc', substring('abc', 1, 2), substring(substring('abcdefg', 4, 3), 1, 2), null") + testFoldConst("SELECT Length('Hello World')") + testFoldConst("SELECT Lower('Hello World')") + testFoldConst("SELECT Upper('Hello World')") + testFoldConst("SELECT Trim(' Hello World ')") + testFoldConst("SELECT Trim('11111', 11)") + testFoldConst("SELECT Ltrim(' Hello World ')") + testFoldConst("SELECT LTrim(' 11111', 11)") + testFoldConst("SELECT LTrim('11111 ', 11)") + testFoldConst("SELECT Rtrim(' Hello World ')") + testFoldConst("SELECT RTrim('11111 ', 11)") + testFoldConst("SELECT RTrim(' 11111', 11)") + testFoldConst("SELECT Replace('Hello World', 'World', 'Everyone')") + testFoldConst("SELECT Left('Hello World', 5)") + testFoldConst("SELECT Right('Hello World', 5)") + testFoldConst("SELECT Locate('World', 'Hello World')") + testFoldConst("SELECT Instr('Hello World', 'World')") + testFoldConst("SELECT Ascii('A')") + testFoldConst("SELECT Bin(5)") + testFoldConst("SELECT Hex(255)") + testFoldConst("SELECT Unhex('FF')") + testFoldConst("SELECT Concat_Ws('-', '2024', '09', '02')") + testFoldConst("SELECT Char(65)") + testFoldConst("SELECT Character_Length('Hello World')") + testFoldConst("SELECT Initcap('hello world')") + testFoldConst("SELECT Md5('Hello World')") + testFoldConst("SELECT Md5Sum('Hello World')") +// testFoldConst("SELECT JsonExtract('{"key": "value"}', '$.key')") +// testFoldConst("SELECT JsonbExtractString('{"key": "value"}', '$.key')") +// testFoldConst("SELECT JsonContains('{"key": "value"}', '"key"')") +// testFoldConst("SELECT JsonLength('{"key1": "value1", "key2": "value2"}')") +// testFoldConst("SELECT JsonObject('key', 'value')") +// testFoldConst("SELECT JsonArray('value1', 'value2')") +// testFoldConst("SELECT JsonKeys('{"key1": "value1", "key2": "value2"}')") +// testFoldConst("SELECT JsonInsert('{"key1": "value1"}', '$.key2', 'value2')") +// testFoldConst("SELECT JsonReplace('{"key1": "value1"}', '$.key1', 'new_value')") +// testFoldConst("SELECT JsonSet('{"key1": "value1"}', '$.key2', 'value2')") +// testFoldConst("SELECT Json_Quote('Hello World')") +// testFoldConst("SELECT Json_UnQuote('"Hello World"')") + testFoldConst("SELECT Field('b', 'a', 'b', 'c')") + testFoldConst("SELECT Find_In_Set('b', 'a,b,c')") + testFoldConst("SELECT Repeat('Hello', 3)") + testFoldConst("SELECT Reverse('Hello')") + testFoldConst("SELECT length(Space(10))") +// testFoldConst("SELECT Split_By_Char('a,b,c',',')") has bug in be execution + testFoldConst("SELECT Split_By_String('a::b::c', '::')") + testFoldConst("SELECT Split_Part('a,b,c', ',', 2)") + testFoldConst("SELECT Substring_Index('a,b,c', ',', 2)") + testFoldConst("SELECT Strcmp('abc', 'abd')") + testFoldConst("SELECT StrLeft('Hello World', 5)") + testFoldConst("SELECT StrRight('Hello World', 5)") + testFoldConst("SELECT Overlay('abcdef', '123', 3, 2)") + testFoldConst("SELECT Parse_Url('http://www.example.com/path?query=abc', 'HOST')") + testFoldConst("SELECT Url_Decode('%20Hello%20World%20')") + + // Substring with negative start index + // Expected behavior: Depending on the SQL engine, might return an empty string or error. + testFoldConst("SELECT Substring('Hello World', -1, 5)") + + // Substring with length exceeding the string length + // Expected behavior: Return 'Hello' as the length exceeds the string length. + testFoldConst("SELECT Substring('Hello', 1, 10)") + + // Left with length greater than string length + // Expected behavior: Return 'Hello'. + testFoldConst("SELECT Left('Hello', 10)") + + // Right with length greater than string length + // Expected behavior: Return 'Hello'. + testFoldConst("SELECT Right('Hello', 10)") + + // SplitPart with part number greater than the number of parts + // Expected behavior: Return an empty string or error. + testFoldConst("SELECT Split_Part('a,b,c', ',', 5)") + + // SplitPart with negative part number + // Expected behavior: Return an empty string or error. + testFoldConst("SELECT Split_Part('a,b,c', ',', -1)") + + // Locate with the substring not present + // Expected behavior: Return 0 as 'World' is not found. + testFoldConst("SELECT Locate('World', 'Hello')") + + // Instr with the substring not present + // Expected behavior: Return 0 as 'World' is not found. + testFoldConst("SELECT Instr('Hello', 'World')") + + // Replace with an empty search string + // Expected behavior: Some SQL engines may treat this as a no-op, others might throw an error. + testFoldConst("SELECT Replace('Hello World', '', 'Everyone')") + + // Replace with an empty replacement string + // Expected behavior: Return 'Hello '. + testFoldConst("SELECT Replace('Hello World', 'World', '')") + + // Concat with NULL values + // Expected behavior: Depending on the SQL engine, may return 'HelloWorld' or NULL. + testFoldConst("SELECT Concat('Hello', NULL, 'World')") + + // Ltrim with a string that has no leading spaces + // Expected behavior: Return 'Hello'. + testFoldConst("SELECT Ltrim('Hello')") + + // Rtrim with a string that has no trailing spaces + // Expected behavior: Return 'Hello'. + testFoldConst("SELECT Rtrim('Hello')") + + // JsonExtract with an invalid JSON path + // Expected behavior: Return NULL or an empty string. +// testFoldConst("SELECT Json_Extract('{"key": "value"}', '$.invalid')") + + // JsonLength with a non-JSON string + // Expected behavior: Return NULL or error. + testFoldConst("SELECT Json_Length('Hello World')") + + // Field with a value not present in the list + // Expected behavior: Return 0 as 'd' is not found. + testFoldConst("SELECT Field('d', 'a', 'b', 'c')") + + // FindInSet with a value not present in the set + // Expected behavior: Return 0 as 'd' is not found. + testFoldConst("SELECT Find_In_Set('d', 'a,b,c')") + + // Repeat with a negative repeat count + // Expected behavior: Return an empty string or error. + testFoldConst("SELECT Repeat('Hello', -3)") + + // Space with a negative number of spaces + // Expected behavior: Return an empty string or error. + testFoldConst("SELECT Space(-5)") + + // SplitByChar with a delimiter not present in the string + // Expected behavior: Return the original string in a single element array. +// testFoldConst("SELECT Split_By_Char('abc', ',')") + + // SplitByString with a delimiter not present in the string + // Expected behavior: Return the original string in a single element array. + testFoldConst("SELECT Split_By_String('::', 'abc')") + + // Strcmp with two identical strings + // Expected behavior: Return 0 as the strings are equal. + testFoldConst("SELECT Strcmp('abc', 'abc')") + + // Strcmp with a null string + // Expected behavior: Return NULL or -1 depending on the SQL engine. + testFoldConst("SELECT Strcmp('abc', NULL)") + + // Hex with a negative number + // Expected behavior: Return the hexadecimal representation of the two's complement, or an error depending on the SQL engine. + testFoldConst("SELECT Hex(-255)") + + // Unhex with an invalid hexadecimal string + // Expected behavior: Return NULL or error as 'GHIJ' is not a valid hex string. + testFoldConst("SELECT Unhex('GHIJ')") + + // JsonReplace with a path that does not exist + // Expected behavior: Depending on the engine, might return the original JSON or an error. +// testFoldConst("SELECT Json_Replace('{"key": "value"}', '$.nonexistent', 'new_value')") + + // UrlDecode with an invalid percent-encoded string + // Expected behavior: Return NULL or error due to invalid encoding. + testFoldConst("SELECT Url_Decode('%ZZHello%20World')") + + testFoldConst("select elt(0, \"hello\", \"doris\")") + testFoldConst("select elt(1, \"hello\", \"doris\")") + testFoldConst("select elt(2, \"hello\", \"doris\")") + testFoldConst("select elt(3, \"hello\", \"doris\")") + testFoldConst("select c1, c2, elt(c1, c2) from (select number as c1, 'varchar' as c2 from numbers('number'='5') where number > 0) a") + + testFoldConst("select append_trailing_char_if_absent('a','c')") + testFoldConst("select append_trailing_char_if_absent('ac','c')") + + testFoldConst("select ascii('1')") + testFoldConst("select ascii('a')") + testFoldConst("select ascii('A')") + testFoldConst("select ascii('!')") + + testFoldConst("select bit_length(\"abc\")") + + testFoldConst("select char_length(\"abc\")") + + testFoldConst("select concat(\"a\", \"b\")") + testFoldConst("select concat(\"a\", \"b\", \"c\")") + testFoldConst("select concat(\"a\", null, \"c\")") + + testFoldConst("select concat_ws(\"or\", \"d\", \"is\")") + testFoldConst("select concat_ws(NULL, \"d\", \"is\")") + testFoldConst("select concat_ws(\"or\", \"d\", NULL,\"is\")") + testFoldConst("select concat_ws(\"or\", [\"d\", \"is\"])") + testFoldConst("select concat_ws(NULL, [\"d\", \"is\"])") + testFoldConst("select concat_ws(\"or\", [\"d\", NULL,\"is\"])") + testFoldConst("select concat_ws(\"or\", [\"d\", \"\",\"is\"])") + + testFoldConst("select ends_with(\"Hello doris\", \"doris\")") + testFoldConst("select ends_with(\"Hello doris\", \"Hello\")") + + testFoldConst("select find_in_set(\"b\", \"a,b,c\")") + testFoldConst("select find_in_set(\"d\", \"a,b,c\")") + testFoldConst("select find_in_set(null, \"a,b,c\")") + testFoldConst("select find_in_set(\"a\", null)") + + testFoldConst("select hex('1')") + testFoldConst("select hex('12')") + testFoldConst("select hex('@')") + testFoldConst("select hex('A')") + testFoldConst("select hex(12)") + testFoldConst("select hex(-1)") + testFoldConst("select hex('hello,doris')") + + testFoldConst("select unhex('@')") + testFoldConst("select unhex('68656C6C6F2C646F726973')") + testFoldConst("select unhex('41')") + testFoldConst("select unhex('4142')") + testFoldConst("select unhex('')") + testFoldConst("select unhex(NULL)") + + testFoldConst("select instr(\"abc\", \"b\")") + testFoldConst("select instr(\"abc\", \"d\")") + testFoldConst("select instr(\"abc\", null)") + testFoldConst("select instr(null, \"a\")") + testFoldConst("SELECT instr('foobar', '')") + testFoldConst("SELECT instr('上海天津北京杭州', '北京')") + + testFoldConst("SELECT lcase(\"AbC123\")") + testFoldConst("SELECT lower(\"AbC123\")") + + testFoldConst("SELECT initcap(\"AbC123abc abc.abc,?|abc\")") + + testFoldConst("select left(\"Hello doris\",5)") + testFoldConst("select right(\"Hello doris\",5)") + + testFoldConst("select length(\"abc\")") + + testFoldConst("SELECT LOCATE('bar', 'foobarbar')") + testFoldConst("SELECT LOCATE('xbar', 'foobar')") + testFoldConst("SELECT LOCATE('', 'foobar')") + testFoldConst("SELECT LOCATE('北京', '上海天津北京杭州')") + + testFoldConst("SELECT lpad(\"hi\", 5, \"xy\")") + testFoldConst("SELECT lpad(\"hi\", 1, \"xy\")") + testFoldConst("SELECT rpad(\"hi\", 5, \"xy\")") + testFoldConst("SELECT rpad(\"hi\", 1, \"xy\")") + + testFoldConst("SELECT ltrim(' ab d')") + + testFoldConst("select money_format(17014116)") + testFoldConst("select money_format(1123.456)") + testFoldConst("select money_format(1123.4)") + testFoldConst("select money_format(truncate(1000,10))") + + testFoldConst("select null_or_empty(null)") + testFoldConst("select null_or_empty(\"\")") + testFoldConst("select null_or_empty(\"a\")") + + testFoldConst("select not_null_or_empty(null)") + testFoldConst("select not_null_or_empty(\"\")") + testFoldConst("select not_null_or_empty(\"a\")") + + testFoldConst("SELECT repeat(\"a\", 3)") + testFoldConst("SELECT repeat(\"a\", -1)") + testFoldConst("SELECT repeat(\"a\", 0)") + testFoldConst("SELECT repeat(\"a\",null)") + testFoldConst("SELECT repeat(null,1)") + + testFoldConst("select replace(\"https://doris.apache.org:9090\", \":9090\", \"\")") + testFoldConst("select replace(\"https://doris.apache.org:9090\", \"\", \"new_str\")") + + testFoldConst("SELECT REVERSE('hello')") + + testFoldConst("select split_part('hello world', ' ', 1)") + testFoldConst("select split_part('hello world', ' ', 2)") + testFoldConst("select split_part('hello world', ' ', 0)") + testFoldConst("select split_part('hello world', ' ', -1)") + testFoldConst("select split_part('hello world', ' ', -2)") + testFoldConst("select split_part('hello world', ' ', -3)") + testFoldConst("select split_part('abc##123###xyz', '##', 0)") + testFoldConst("select split_part('abc##123###xyz', '##', 1)") + testFoldConst("select split_part('abc##123###xyz', '##', 3)") + testFoldConst("select split_part('abc##123###xyz', '##', 5)") + testFoldConst("select split_part('abc##123###xyz', '##', -1)") + testFoldConst("select split_part('abc##123###xyz', '##', -2)") + testFoldConst("select split_part('abc##123###xyz', '##', -4)") + + testFoldConst("select starts_with(\"hello world\",\"hello\")") + testFoldConst("select starts_with(\"hello world\",\"world\")") + testFoldConst("select starts_with(\"hello world\",null)") + + testFoldConst("select strleft(NULL, 1)") + testFoldConst("select strleft(\"good morning\", NULL)") + testFoldConst("select left(NULL, 1)") + testFoldConst("select left(\"good morning\", NULL)") + testFoldConst("select strleft(\"Hello doris\", 5)") + testFoldConst("select left(\"Hello doris\", 5)") + testFoldConst("select strright(NULL, 1)") + testFoldConst("select strright(\"good morning\", NULL)") + testFoldConst("select right(NULL, 1)") + testFoldConst("select right(\"good morning\", NULL)") + testFoldConst("select strright(\"Hello doris\", 5)") + testFoldConst("select right(\"Hello doris\", 5)") + testFoldConst("select strleft(\"good morning\", 120)") + testFoldConst("select strleft(\"good morning\", -5)") + testFoldConst("select strright(\"Hello doris\", 120)") + testFoldConst("select strright(\"Hello doris\", -5)") + testFoldConst("select left(\"good morning\", 120)") + testFoldConst("select left(\"good morning\", -5)") + testFoldConst("select right(\"Hello doris\", 120)") + testFoldConst("select right(\"Hello doris\", -6)") + + testFoldConst("select substring('abc1', 2)") + testFoldConst("select substring('abc1', -2)") + testFoldConst("select substring('abc1', 5)") + testFoldConst("select substring('abc1def', 2, 2)") + testFoldConst("select substring('abcdef',3,-1)") + testFoldConst("select substring('abcdef',-3,-1)") + testFoldConst("select substring('abcdef',10,1)") + + testFoldConst("select substr('a',3,1)") + testFoldConst("select substr('a',2,1)") + testFoldConst("select substr('a',1,1)") + testFoldConst("select substr('a',0,1)") + testFoldConst("select substr('a',-1,1)") + testFoldConst("select substr('a',-2,1)") + testFoldConst("select substr('a',-3,1)") + testFoldConst("select substr('abcdef',3,-1)") + testFoldConst("select substr('abcdef',-3,-1)") + + testFoldConst("select sub_replace(\"this is origin str\",\"NEW-STR\",1)") + testFoldConst("select sub_replace(\"doris\",\"***\",1,2)") + + testFoldConst("select substring_index(\"hello world\", \" \", 1)") + testFoldConst("select substring_index(\"hello world\", \" \", 2)") + testFoldConst("select substring_index(\"hello world\", \" \", 3)") + testFoldConst("select substring_index(\"hello world\", \" \", -1)") + testFoldConst("select substring_index(\"hello world\", \" \", -2)") + testFoldConst("select substring_index(\"hello world\", \" \", -3)") + testFoldConst("select substring_index(\"prefix__string2\", \"__\", 2)") + testFoldConst("select substring_index(\"prefix__string2\", \"_\", 2)") + testFoldConst("select substring_index(\"prefix_string2\", \"__\", 1)") + testFoldConst("select substring_index(null, \"__\", 1)") + testFoldConst("select substring_index(\"prefix_string\", null, 1)") + testFoldConst("select substring_index(\"prefix_string\", \"_\", null)") + testFoldConst("select substring_index(\"prefix_string\", \"__\", -1)") + + testFoldConst("select elt(0, \"hello\", \"doris\")") + testFoldConst("select elt(1, \"hello\", \"doris\")") + testFoldConst("select elt(2, \"hello\", \"doris\")") + testFoldConst("select elt(3, \"hello\", \"doris\")") + + testFoldConst("select sub_replace(\"this is origin str\",\"NEW-STR\",1)") + testFoldConst("select sub_replace(\"doris\",\"***\",1,2)") + + testFoldConst("select strcmp('a', 'abc')") + testFoldConst("select strcmp('abc', 'abc')") + testFoldConst("select strcmp('abcd', 'abc')") + + testFoldConst("SELECT Concat(cast('Hello' as string), cast(' ' as string), cast('World' as string))") + testFoldConst("SELECT Substring(cast('Hello World' as string), 1, 5)") + testFoldConst("SELECT Substring(cast('1' as string), 1, 1)") + testFoldConst("SELECT 100, cast('abc' as string), Substring(cast('abc' as string), 1, 2), Substring(Substring(cast('abcdefg' as string), 4, 3), 1, 2), null") + testFoldConst("SELECT Length(cast('Hello World' as string))") + testFoldConst("SELECT Lower(cast('Hello World' as string))") + testFoldConst("SELECT Upper(cast('Hello World' as string))") + testFoldConst("SELECT Trim(cast(' Hello World ' as string))") + testFoldConst("SELECT Trim(cast('11111' as string), cast(11 as string))") + testFoldConst("SELECT Ltrim(cast(' Hello World ' as string))") + testFoldConst("SELECT LTrim(cast(' 11111' as string), cast(11 as string))") + testFoldConst("SELECT LTrim(cast('11111 ' as string), cast(11 as string))") + testFoldConst("SELECT Rtrim(cast(' Hello World ' as string))") + testFoldConst("SELECT RTrim(cast('11111 ' as string), cast(11 as string))") + testFoldConst("SELECT RTrim(cast(' 11111' as string), cast(11 as string))") + testFoldConst("SELECT Replace(cast('Hello World' as string), cast('World' as string), cast('Everyone' as string))") + testFoldConst("SELECT Left(cast('Hello World' as string), 5)") + testFoldConst("SELECT Right(cast('Hello World' as string), 5)") + testFoldConst("SELECT Locate(cast('World' as string), cast('Hello World' as string))") + testFoldConst("SELECT Instr(cast('Hello World' as string), cast('World' as string))") + testFoldConst("SELECT Ascii(cast('A' as string))") + testFoldConst("SELECT Bin(5)") + testFoldConst("SELECT Hex(255)") + testFoldConst("SELECT Unhex(cast('FF' as string))") +// testFoldConst("SELECT Concat_Ws(cast('-' as string), cast('2024' as string), cast('09' as string), cast('02' as string))") + testFoldConst("SELECT Char(65)") + testFoldConst("SELECT Character_Length(cast('Hello World' as string))") + testFoldConst("SELECT Initcap(cast('hello world' as string))") + testFoldConst("SELECT Md5(cast('Hello World' as string))") +// testFoldConst("SELECT Md5Sum(cast('Hello World' as string))") +// testFoldConst("SELECT JsonExtract(cast('{\"key\": \"value\"}' as string), cast('$.key' as string))") +// testFoldConst("SELECT JsonbExtractString(cast('{\"key\": \"value\"}' as string), cast('$.key' as string))") +// testFoldConst("SELECT JsonContains(cast('{\"key\": \"value\"}' as string), cast('\"key\"' as string))") +// testFoldConst("SELECT JsonLength(cast('{\"key1\": \"value1\", \"key2\": \"value2\"}' as string))") +// testFoldConst("SELECT JsonObject(cast('key' as string), cast('value' as string))") +// testFoldConst("SELECT JsonArray(cast('value1' as string), cast('value2' as string))") +// testFoldConst("SELECT JsonKeys(cast('{\"key1\": \"value1\", \"key2\": \"value2\"}' as string))") +// testFoldConst("SELECT JsonInsert(cast('{\"key1\": \"value1\"}' as string), cast('$.key2' as string), cast('value2' as string))") +// testFoldConst("SELECT JsonReplace(cast('{\"key1\": \"value1\"}' as string), cast('$.key1' as string), cast('new_value' as string))") +// testFoldConst("SELECT JsonSet(cast('{\"key1\": \"value1\"}' as string), cast('$.key2' as string), cast('value2' as string))") +// testFoldConst("SELECT Json_Quote(cast('Hello World' as string))") +// testFoldConst("SELECT Json_UnQuote(cast('\"Hello World\"' as string))") +// testFoldConst("SELECT Field(cast('b' as string), cast('a' as string), cast('b' as string), cast('c' as string))") + testFoldConst("SELECT Find_In_Set(cast('b' as string), cast('a,b,c' as string))") + testFoldConst("SELECT Repeat(cast('Hello' as string), 3)") + testFoldConst("SELECT Reverse(cast('Hello' as string))") + testFoldConst("SELECT length(Space(10))") +// testFoldConst("SELECT Split_By_Char(cast('a,b,c' as string), cast(',' as string))") has bug in be execution + testFoldConst("SELECT Split_By_String(cast('a::b::c' as string), cast('::' as string))") + testFoldConst("SELECT Split_Part(cast('a,b,c' as string), cast(',' as string), 2)") + testFoldConst("SELECT Substring_Index(cast('a,b,c' as string), cast(',' as string), 2)") + testFoldConst("SELECT Strcmp(cast('abc' as string), cast('abd' as string))") + testFoldConst("SELECT StrLeft(cast('Hello World' as string), 5)") + testFoldConst("SELECT StrRight(cast('Hello World' as string), 5)") + testFoldConst("SELECT Overlay(cast('abcdef' as string), cast('123' as string), 3, 2)") + testFoldConst("SELECT Parse_Url(cast('http://www.example.com/path?query=abc' as string), cast('HOST' as string))") + testFoldConst("SELECT Url_Decode(cast('%20Hello%20World%20' as string))") + +// Substring with negative start index +// Expected behavior: Depending on the SQL engine, might return an empty string or error. + testFoldConst("SELECT Substring(cast('Hello World' as string), -1, 5)") + +// Substring with length exceeding the string length +// Expected behavior: Return 'Hello' as the length exceeds the string length. + testFoldConst("SELECT Substring(cast('Hello' as string), 1, 10)") + +// Left with length greater than string length +// Expected behavior: Return 'Hello'. + testFoldConst("SELECT Left(cast('Hello' as string), 10)") + +// Right with length greater than string length +// Expected behavior: Return 'Hello'. + testFoldConst("SELECT Right(cast('Hello' as string), 10)") + +// SplitPart with part number greater than the number of parts +// Expected behavior: Return an empty string or error. + testFoldConst("SELECT Split_Part(cast('a,b,c' as string), cast(',' as string), 5)") + +// SplitPart with negative part number +// Expected behavior: Return an empty string or error. + testFoldConst("SELECT Split_Part(cast('a,b,c' as string), cast(',' as string), -1)") + +// Locate with the substring not present +// Expected behavior: Return 0 as 'World' is not found. + testFoldConst("SELECT Locate(cast('World' as string), cast('Hello' as string))") + +// Instr with the substring not present +// Expected behavior: Return 0 as 'World' is not found. + testFoldConst("SELECT Instr(cast('Hello' as string), cast('World' as string))") + +// Replace with an empty search string +// Expected behavior: Some SQL engines may treat this as a no-op, others might throw an error. + testFoldConst("SELECT Replace(cast('Hello World' as string), '', cast('Everyone' as string))") + +// Replace with an empty replacement string +// Expected behavior: Return 'Hello '. + testFoldConst("SELECT Replace(cast('Hello World' as string), cast('World' as string), '')") + +// Concat with NULL values +// Expected behavior: Depending on the SQL engine, may return 'HelloWorld' or NULL. + testFoldConst("SELECT Concat(cast('Hello' as string), NULL, cast('World' as string))") + +// Ltrim with a string that has no leading spaces +// Expected behavior: Return 'Hello'. + testFoldConst("SELECT Ltrim(cast('Hello' as string))") + +// Rtrim with a string that has no trailing spaces +// Expected behavior: Return 'Hello'. + testFoldConst("SELECT Rtrim(cast('Hello' as string))") + +// Testing JSON Length function with a non-JSON string + testFoldConst("SELECT Json_Length(cast('Hello World' as string))") + +// Field with a value not present in the list +// testFoldConst("SELECT Field(cast('d' as string), cast('a' as string), cast('b' as string), cast('c' as string))") + +// FindInSet with a value not present in the set + testFoldConst("SELECT Find_In_Set(cast('d' as string), cast('a,b,c' as string))") + +// Repeat with a negative repeat count + testFoldConst("SELECT Repeat(cast('Hello' as string), -3)") + +// Space with a negative number of spaces + testFoldConst("SELECT Space(-5)") + +// SplitByChar with a delimiter not present in the string +// testFoldConst("SELECT Split_By_Char(cast('abc' as string), cast(',' as string))") + +// SplitByString with a delimiter not present in the string + testFoldConst("SELECT Split_By_String(cast('abc' as string), cast('::' as string))") + +// Strcmp with two identical strings + testFoldConst("SELECT Strcmp(cast('abc' as string), cast('abc' as string))") + +// Strcmp with a null string + testFoldConst("SELECT Strcmp(cast('abc' as string), NULL)") + +// Hex with a negative number + testFoldConst("SELECT Hex(-255)") + +// Unhex with an invalid hexadecimal string + testFoldConst("SELECT Unhex(cast('GHIJ' as string))") + +// UrlDecode with an invalid percent-encoded string + testFoldConst("SELECT Url_Decode(cast('%ZZHello%20World' as string))") + +// Additional function tests + testFoldConst("SELECT Elt(0, cast('hello' as string), cast('doris' as string))") + testFoldConst("SELECT Elt(1, cast('hello' as string), cast('doris' as string))") + testFoldConst("SELECT Elt(2, cast('hello' as string), cast('doris' as string))") + testFoldConst("SELECT Elt(3, cast('hello' as string), cast('doris' as string))") + testFoldConst("SELECT Append_Trailing_Char_If_Absent(cast('a' as string), cast('c' as string))") + testFoldConst("SELECT Append_Trailing_Char_If_Absent(cast('ac' as string), cast('c' as string))") + testFoldConst("SELECT Ascii(cast('1' as string))") + testFoldConst("SELECT Ascii(cast('a' as string))") + testFoldConst("SELECT Ascii(cast('A' as string))") + testFoldConst("SELECT Ascii(cast('!' as string))") + testFoldConst("SELECT Bit_Length(cast('abc' as string))") + testFoldConst("SELECT Char_Length(cast('abc' as string))") + testFoldConst("SELECT Concat(cast('a' as string), cast('b' as string))") + testFoldConst("SELECT Concat(cast('a' as string), cast('b' as string), cast('c' as string))") + testFoldConst("SELECT Concat(cast('a' as string), NULL, cast('c' as string))") +// testFoldConst("SELECT Concat_Ws(cast('or' as string), cast('d' as string), cast('is' as string))") +// testFoldConst("SELECT Concat_Ws(NULL, cast('d' as string), cast('is' as string))") +// testFoldConst("SELECT Concat_Ws(cast('or' as string), cast('d' as string), NULL, cast('is' as string))") +// testFoldConst("SELECT Concat_Ws(cast('or' as string), cast('d' as string), cast('' as string), cast('is' as string))") + testFoldConst("SELECT Ends_With(cast('Hello doris' as string), cast('doris' as string))") + testFoldConst("SELECT Ends_With(cast('Hello doris' as string), cast('Hello' as string))") + testFoldConst("SELECT Find_In_Set(cast('b' as string), cast('a,b,c' as string))") + testFoldConst("SELECT Find_In_Set(cast('d' as string), cast('a,b,c' as string))") + testFoldConst("SELECT Find_In_Set(NULL, cast('a,b,c' as string))") + testFoldConst("SELECT Find_In_Set(cast('a' as string), NULL)") + testFoldConst("SELECT Hex(cast('1' as string))") + testFoldConst("SELECT Hex(cast('12' as string))") + testFoldConst("SELECT Hex(cast('@' as string))") + testFoldConst("SELECT Hex(cast('A' as string))") + testFoldConst("SELECT Hex(12)") + testFoldConst("SELECT Hex(-1)") + testFoldConst("SELECT Hex(cast('hello,doris' as string))") + testFoldConst("SELECT Unhex(cast('@' as string))") + testFoldConst("SELECT Unhex(cast('68656C6C6F2C646F726973' as string))") + testFoldConst("SELECT Unhex(cast('41' as string))") + testFoldConst("SELECT Unhex(cast('4142' as string))") + testFoldConst("SELECT Unhex(cast('' as string))") + testFoldConst("SELECT Unhex(NULL)") + testFoldConst("SELECT Instr(cast('abc' as string), cast('b' as string))") + testFoldConst("SELECT Instr(cast('abc' as string), cast('d' as string))") + testFoldConst("SELECT Instr(cast('abc' as string), NULL)") + testFoldConst("SELECT Instr(NULL, cast('a' as string))") + testFoldConst("SELECT Lcase(cast('AbC123' as string))") + testFoldConst("SELECT Lower(cast('AbC123' as string))") + testFoldConst("SELECT Initcap(cast('AbC123abc abc.abc,?|abc' as string))") + testFoldConst("SELECT Left(cast('Hello doris' as string), 5)") + testFoldConst("SELECT Right(cast('Hello doris' as string), 5)") + testFoldConst("SELECT Length(cast('abc' as string))") + testFoldConst("SELECT LOCATE(cast('bar' as string), cast('foobarbar' as string))") + testFoldConst("SELECT LOCATE(cast('xbar' as string), cast('foobar' as string))") + testFoldConst("SELECT LOCATE(cast('' as string), cast('foobar' as string))") + testFoldConst("SELECT LOCATE(cast('北京' as string), cast('上海天津北京杭州' as string))") + testFoldConst("SELECT Lpad(cast('hi' as string), 5, cast('xy' as string))") + testFoldConst("SELECT Lpad(cast('hi' as string), 1, cast('xy' as string))") + testFoldConst("SELECT Rpad(cast('hi' as string), 5, cast('xy' as string))") + testFoldConst("SELECT Rpad(cast('hi' as string), 1, cast('xy' as string))") + testFoldConst("SELECT Ltrim(cast(' ab d' as string))") + testFoldConst("SELECT Money_Format(17014116)") + testFoldConst("SELECT Money_Format(1123.456)") + testFoldConst("SELECT Money_Format(1123.4)") + testFoldConst("SELECT Money_Format(Truncate(1000,10))") + testFoldConst("SELECT Null_Or_Empty(NULL)") + testFoldConst("SELECT Null_Or_Empty(cast('' as string))") + testFoldConst("SELECT Null_Or_Empty(cast('a' as string))") + testFoldConst("SELECT Not_Null_Or_Empty(NULL)") + testFoldConst("SELECT Not_Null_Or_Empty(cast('' as string))") + testFoldConst("SELECT Not_Null_Or_Empty(cast('a' as string))") + testFoldConst("SELECT Repeat(cast('a' as string), 3)") + testFoldConst("SELECT Repeat(cast('a' as string), -1)") + testFoldConst("SELECT Repeat(cast('a' as string), 0)") + testFoldConst("SELECT Repeat(NULL, 1)") + testFoldConst("SELECT Replace(cast('https://doris.apache.org:9090' as string), cast(':9090' as string), cast('' as string))") + testFoldConst("SELECT Replace(cast('https://doris.apache.org:9090' as string), cast('' as string), cast('new_str' as string))") + testFoldConst("SELECT REVERSE(cast('hello' as string))") + testFoldConst("SELECT Split_Part(cast('hello world' as string), cast(' ' as string), 1)") + testFoldConst("SELECT Split_Part(cast('hello world' as string), cast(' ' as string), 2)") + testFoldConst("SELECT Split_Part(cast('hello world' as string), cast(' ' as string), 3)") + testFoldConst("SELECT Concat(CAST('Hello' AS STRING), CAST(' ' AS STRING), CAST('World' AS STRING))") + testFoldConst("SELECT Concat(CAST('Hello' AS STRING), CAST(NULL AS STRING))") + testFoldConst("SELECT Concat(CAST(NULL AS STRING), CAST('World' AS STRING))") + + testFoldConst("SELECT Starts_With(CAST('hello world' AS STRING), CAST('hello' AS STRING))") + testFoldConst("SELECT Starts_With(CAST('hello world' AS STRING), CAST('world' AS STRING))") + testFoldConst("SELECT Starts_With(CAST('hello world' AS STRING), CAST(NULL AS STRING))") + + testFoldConst("SELECT StrLeft(CAST(NULL AS STRING), 1)") + testFoldConst("SELECT StrLeft(CAST('good morning' AS STRING), NULL)") + testFoldConst("SELECT Left(CAST(NULL AS STRING), 1)") + testFoldConst("SELECT Left(CAST('good morning' AS STRING), NULL)") + testFoldConst("SELECT StrLeft(CAST('Hello doris' AS STRING), 5)") + testFoldConst("SELECT Left(CAST('Hello doris' AS STRING), 5)") + testFoldConst("SELECT StrRight(CAST(NULL AS STRING), 1)") + testFoldConst("SELECT StrRight(CAST('good morning' AS STRING), NULL)") + testFoldConst("SELECT Right(CAST(NULL AS STRING), 1)") + testFoldConst("SELECT Right(CAST('good morning' AS STRING), NULL)") + testFoldConst("SELECT StrRight(CAST('Hello doris' AS STRING), 5)") + testFoldConst("SELECT Right(CAST('Hello doris' AS STRING), 5)") + testFoldConst("SELECT StrLeft(CAST('good morning' AS STRING), 120)") + testFoldConst("SELECT StrLeft(CAST('good morning' AS STRING), -5)") + testFoldConst("SELECT StrRight(CAST('Hello doris' AS STRING), 120)") + testFoldConst("SELECT StrRight(CAST('Hello doris' AS STRING), -5)") + testFoldConst("SELECT Left(CAST('good morning' AS STRING), 120)") + testFoldConst("SELECT Left(CAST('good morning' AS STRING), -5)") + testFoldConst("SELECT Right(CAST('Hello doris' AS STRING), 120)") + testFoldConst("SELECT Right(CAST('Hello doris' AS STRING), -6)") + + testFoldConst("SELECT Substring(CAST('abc1' AS STRING), 2)") + testFoldConst("SELECT Substring(CAST('abc1' AS STRING), -2)") + testFoldConst("SELECT Substring(CAST('abc1' AS STRING), 5)") + testFoldConst("SELECT Substring(CAST('abc1def' AS STRING), 2, 2)") + testFoldConst("SELECT Substring(CAST('abcdef' AS STRING), 3, -1)") + testFoldConst("SELECT Substring(CAST('abcdef' AS STRING), -3, -1)") + testFoldConst("SELECT Substring(CAST('abcdef' AS STRING), 10, 1)") + + testFoldConst("SELECT Substr(CAST('a' AS STRING), 3, 1)") + testFoldConst("SELECT Substr(CAST('a' AS STRING), 2, 1)") + testFoldConst("SELECT Substr(CAST('a' AS STRING), 1, 1)") + testFoldConst("SELECT Substr(CAST('a' AS STRING), 0, 1)") + testFoldConst("SELECT Substr(CAST('a' AS STRING), -1, 1)") + testFoldConst("SELECT Substr(CAST('a' AS STRING), -2, 1)") + testFoldConst("SELECT Substr(CAST('a' AS STRING), -3, 1)") + testFoldConst("SELECT Substr(CAST('abcdef' AS STRING), 3, -1)") + testFoldConst("SELECT Substr(CAST('abcdef' AS STRING), -3, -1)") + + testFoldConst("SELECT Sub_Replace(CAST('this is origin str' AS STRING), CAST('NEW-STR' AS STRING), 1)") + testFoldConst("SELECT Sub_Replace(CAST('doris' AS STRING), CAST('***' AS STRING), 1, 2)") + + testFoldConst("SELECT Substring_Index(CAST('hello world' AS STRING), CAST(' ' AS STRING), 1)") + testFoldConst("SELECT Substring_Index(CAST('hello world' AS STRING), CAST(' ' AS STRING), 2)") + testFoldConst("SELECT Substring_Index(CAST('hello world' AS STRING), CAST(' ' AS STRING), 3)") + testFoldConst("SELECT Substring_Index(CAST('hello world' AS STRING), CAST(' ' AS STRING), -1)") + testFoldConst("SELECT Substring_Index(CAST('hello world' AS STRING), CAST(' ' AS STRING), -2)") + testFoldConst("SELECT Substring_Index(CAST('hello world' AS STRING), CAST(' ' AS STRING), -3)") + testFoldConst("SELECT Substring_Index(CAST('prefix__string2' AS STRING), CAST('__' AS STRING), 2)") + testFoldConst("SELECT Substring_Index(CAST('prefix__string2' AS STRING), CAST('_' AS STRING), 2)") + testFoldConst("SELECT Substring_Index(CAST('prefix_string2' AS STRING), CAST('__' AS STRING), 1)") + testFoldConst("SELECT Substring_Index(CAST(NULL AS STRING), CAST('__' AS STRING), 1)") + testFoldConst("SELECT Substring_Index(CAST('prefix_string' AS STRING), CAST(NULL AS STRING), 1)") + testFoldConst("SELECT Substring_Index(CAST('prefix_string' AS STRING), CAST('_' AS STRING), NULL)") + testFoldConst("SELECT Substring_Index(CAST('prefix_string' AS STRING), CAST('__' AS STRING), -1)") + + testFoldConst("SELECT Elt(0, CAST('hello' AS STRING), CAST('doris' AS STRING))") + testFoldConst("SELECT Elt(1, CAST('hello' AS STRING), CAST('doris' AS STRING))") + testFoldConst("SELECT Elt(2, CAST('hello' AS STRING), CAST('doris' AS STRING))") + testFoldConst("SELECT Elt(3, CAST('hello' AS STRING), CAST('doris' AS STRING))") + + testFoldConst("SELECT Sub_Replace(CAST('this is origin str' AS STRING), CAST('NEW-STR' AS STRING), 1)") + testFoldConst("SELECT Sub_Replace(CAST('doris' AS STRING), CAST('***' AS STRING), 1, 2)") + + testFoldConst("SELECT StrCmp(CAST('a' AS STRING), CAST('abc' AS STRING))") + testFoldConst("SELECT StrCmp(CAST('abc' AS STRING), CAST('abc' AS STRING))") + testFoldConst("SELECT StrCmp(CAST('abcd' AS STRING), CAST('abc' AS STRING))") + + // fix problem of cast date and time function exception + testFoldConst("select ifnull(date_format(CONCAT_WS('', '9999-07', '-00'), '%Y-%m'),3)") + +}