class SubstrFunction extends Scalar<String, Object> {

    public static final String NAME = "substr";
    public static final String ALIAS = "substring";

    public static void register(Functions.Builder builder) {<FILL_FUNCTION_BODY>}

    private SubstrFunction(Signature signature, BoundSignature boundSignature) {
        super(signature, boundSignature);
    }

    @Override
    public String evaluate(TransactionContext txnCtx, NodeContext nodeCtx, Input[] args) {
        assert args.length == 2 || args.length == 3 : "number of arguments must be 2 or 3";
        String val = (String) args[0].value();
        if (val == null) {
            return null;
        }
        Number beginIdx = (Number) args[1].value();
        if (beginIdx == null) {
            return null;
        }
        if (args.length == 3) {
            Number len = (Number) args[2].value();
            if (len == null) {
                return null;
            }
            return evaluate(val, (beginIdx).intValue(), len.intValue());

        }
        return evaluate(val, (beginIdx).intValue());
    }

    private static String evaluate(@NotNull String inputStr, int beginIdx) {
        final int startPos = Math.max(0, beginIdx - 1);
        if (startPos > inputStr.length() - 1) {
            return "";
        }
        int endPos = inputStr.length();
        return substring(inputStr, startPos, endPos);
    }

    @VisibleForTesting
    static String evaluate(@NotNull String inputStr, int beginIdx, int len) {
        final int startPos = Math.max(0, beginIdx - 1);
        if (startPos > inputStr.length() - 1) {
            return "";
        }
        int endPos = inputStr.length();
        if (startPos + len < endPos) {
            endPos = startPos + len;
        }
        return substring(inputStr, startPos, endPos);
    }

    @VisibleForTesting
    static String substring(String value, int begin, int end) {
        return value.substring(begin, end);
    }

    private static class SubstrExtractFunction extends Scalar<String, String> {

        SubstrExtractFunction(Signature signature, BoundSignature boundSignature) {
            super(signature, boundSignature);
        }

        @Override
        @SafeVarargs
        public final String evaluate(TransactionContext txnCtx, NodeContext nodeContext, Input<String>... args) {
            String string = args[0].value();
            if (string == null) {
                return null;
            }
            String pattern = args[1].value();
            if (pattern == null) {
                return null;
            }

            Pattern re = Pattern.compile(pattern);
            Matcher matcher = re.matcher(string);
            int groupCount = matcher.groupCount();
            if (matcher.find()) {
                String group = matcher.group(groupCount > 0 ? 1 : 0);
                return group;
            } else {
                return null;
            }
        }

        @Override
        public Scalar<String, String> compile(List<Symbol> arguments, String currentUser, Roles roles) {
            Symbol patternSymbol = arguments.get(1);
            if (patternSymbol instanceof Input<?> input) {
                String pattern = (String) input.value();
                if (pattern == null) {
                    return this;
                }
                return new CompiledSubstr(signature, boundSignature, pattern);
            }
            return this;
        }
    }

    private static class CompiledSubstr extends Scalar<String, String> {

        private final Matcher matcher;

        public CompiledSubstr(Signature signature, BoundSignature boundSignature, String pattern) {
            super(signature, boundSignature);
            this.matcher = Pattern.compile(pattern).matcher("");
        }


        @Override
        @SafeVarargs
        public final String evaluate(TransactionContext txnCtx, NodeContext nodeContext, Input<String>... args) {
            String string = args[0].value();
            if (string == null) {
                return null;
            }
            matcher.reset(string);
            int groupCount = matcher.groupCount();
            if (matcher.find()) {
                String group = matcher.group(groupCount > 0 ? 1 : 0);
                return group;
            } else {
                return null;
            }
        }
    }
}

        TypeSignature stringType = DataTypes.STRING.getTypeSignature();
        TypeSignature intType = DataTypes.INTEGER.getTypeSignature();
        for (var name : List.of(NAME, ALIAS)) {
            builder.add(
                Signature.scalar(name, stringType, intType, stringType),
                SubstrFunction::new
            );
            builder.add(
                Signature.scalar(name, stringType, intType, intType, stringType),
                SubstrFunction::new
            );
            builder.add(
                Signature.scalar(name, stringType, stringType, stringType),
                SubstrExtractFunction::new
            );
        }
    
<methods>public BoundSignature boundSignature() ,public Scalar<java.lang.String,java.lang.Object> compile(List<io.crate.expression.symbol.Symbol>, java.lang.String, io.crate.role.Roles) ,public transient abstract java.lang.String evaluate(io.crate.metadata.TransactionContext, io.crate.metadata.NodeContext, Input<java.lang.Object>[]) ,public io.crate.expression.symbol.Symbol normalizeSymbol(io.crate.expression.symbol.Function, io.crate.metadata.TransactionContext, io.crate.metadata.NodeContext) ,public io.crate.metadata.functions.Signature signature() <variables>public static final Set<io.crate.metadata.Scalar.Feature> DETERMINISTIC_AND_COMPARISON_REPLACEMENT,public static final Set<io.crate.metadata.Scalar.Feature> DETERMINISTIC_ONLY,public static final Set<io.crate.metadata.Scalar.Feature> NO_FEATURES,protected final non-sealed BoundSignature boundSignature,protected final non-sealed io.crate.metadata.functions.Signature signature