class LikeOperator extends Operator<String> {

    private final FourPredicate<String, String, Character, CaseSensitivity> matcher;
    private final CaseSensitivity caseSensitivity;

    private final java.util.function.Function<Input<String>[], Character> escapeFromInputs;
    private final java.util.function.Function<List<Symbol>, Character> escapeFromSymbols;

    private final int evaluateArgsCount;

    public LikeOperator(Signature signature,
                        BoundSignature boundSignature,
                        FourPredicate<String, String, Character, CaseSensitivity> matcher,
                        CaseSensitivity caseSensitivity) {
        super(signature, boundSignature);
        this.matcher = matcher;
        this.caseSensitivity = caseSensitivity;
        this.evaluateArgsCount = signature.getArgumentDataTypes().size();
        if (signature.getArgumentDataTypes().size() == 3) {
            escapeFromInputs = inputs -> validateAndGetEscape(inputs[2]);
            escapeFromSymbols = symbols -> {
                Symbol escape = symbols.get(2);
                assert escape instanceof Literal<?> : "Escape character must be a literal";
                return validateAndGetEscape((Literal<?>) escape);
            };
        } else {
            // BWC branch for pre-5.6 signature without ESCAPE.
            escapeFromInputs = (ignored) -> LikeOperators.DEFAULT_ESCAPE;
            escapeFromSymbols = (ignored) -> LikeOperators.DEFAULT_ESCAPE;
        }
    }


    /**
     * @return escape character.
     * NULL indicates disabled escaping.
     */
    @Nullable
    private static Character validateAndGetEscape(Input<?> escapeInput) {
        String value = (String) escapeInput.value();
        if (value.length() > 1) {
            throw new IllegalArgumentException("ESCAPE must be a single character");
        }
        if (value.isEmpty()) {
            return null;
        }
        return value.charAt(0);
    }

    @Override
    public Scalar<Boolean, String> compile(List<Symbol> arguments, String userName, Roles roles) {<FILL_FUNCTION_BODY>}

    @Override
    public Boolean evaluate(TransactionContext txnCtx, NodeContext nodeCtx, Input<String>... args) {
        assert args != null : "args must not be null";
        assert args.length == evaluateArgsCount : "number of args must be " + evaluateArgsCount;

        String expression = args[0].value();
        String pattern = args[1].value();
        if (expression == null || pattern == null) {
            return null;
        }
        Character escapeChar = escapeFromInputs.apply(args);
        return matcher.test(expression, pattern, escapeChar, caseSensitivity);
    }

    @Override
    @Nullable
    public Query toQuery(Function function, LuceneQueryBuilder.Context context) {
        List<Symbol> args = function.arguments();
        if (args.get(0) instanceof Reference ref
            && args.get(1) instanceof Literal<?> patternLiteral
        ) {
            Object value = patternLiteral.value();
            assert value instanceof String
                : "LikeOperator is registered for string types. Value must be a string";
            if (((String) value).isEmpty()) {
                return new TermQuery(new Term(ref.storageIdent(), ""));
            }
            Character escapeChar = escapeFromSymbols.apply(args);
            return caseSensitivity.likeQuery(ref.storageIdent(),
                (String) value,
                escapeChar,
                ref.indexType() != IndexType.NONE
            );
        }
        return null;
    }

    private static class CompiledLike extends Scalar<Boolean, String> {
        private final Pattern pattern;

        CompiledLike(Signature signature,
                     BoundSignature boundSignature,
                     String pattern,
                     Character escapeChar,
                     CaseSensitivity caseSensitivity) {
            super(signature, boundSignature);
            this.pattern = LikeOperators.makePattern(pattern, caseSensitivity, escapeChar);
        }

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

        Symbol pattern = arguments.get(1);
        if (pattern instanceof Input) {
            Object value = ((Input<?>) pattern).value();
            if (value == null) {
                return this;
            }
            Character escapeChar = escapeFromSymbols.apply(arguments);
            return new CompiledLike(signature, boundSignature, (String) value, escapeChar, caseSensitivity);
        }
        return super.compile(arguments, userName, roles);
    
<methods>public io.crate.expression.symbol.Symbol normalizeSymbol(io.crate.expression.symbol.Function, io.crate.metadata.TransactionContext, io.crate.metadata.NodeContext) <variables>public static final java.lang.String PREFIX,public static final io.crate.types.BooleanType RETURN_TYPE