class WithinFunction extends Scalar<Boolean, Object> {

    public static final String NAME = "within";

    public static void register(Functions.Builder module) {
        module.add(
            Signature.scalar(
                NAME,
                DataTypes.GEO_SHAPE.getTypeSignature(),
                DataTypes.GEO_SHAPE.getTypeSignature(),
                DataTypes.BOOLEAN.getTypeSignature()
            ),
            WithinFunction::new
        );
        // Needed to avoid casts on references of `geo_point` and thus to avoid generic function filter on lucene.
        // Coercion must be forbidden, as string representation could be a `geo_shape` and thus must match
        // the other signature
        for (var type : List.of(DataTypes.GEO_SHAPE, DataTypes.STRING, DataTypes.UNTYPED_OBJECT, DataTypes.UNDEFINED)) {
            module.add(
                Signature.scalar(
                    NAME,
                    DataTypes.GEO_POINT.getTypeSignature(),
                    type.getTypeSignature(),
                    DataTypes.BOOLEAN.getTypeSignature()
                ).withForbiddenCoercion(),
                WithinFunction::new
            );
        }
    }

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

    @Override
    @SuppressWarnings("rawtypes")
    public Boolean evaluate(TransactionContext txnCtx, NodeContext nodeCtx, Input[] args) {
        assert args.length == 2 : "number of args must be 2";
        return evaluate(args[0], args[1]);
    }

    public Boolean evaluate(Input<?> leftInput, Input<?> rightInput) {
        Object left = leftInput.value();
        if (left == null) {
            return null;
        }
        Object right = rightInput.value();
        if (right == null) {
            return null;
        }
        return parseLeftShape(left).relate(parseRightShape(right)) == SpatialRelation.WITHIN;
    }

    @SuppressWarnings("unchecked")
    private static Shape parseLeftShape(Object left) {
        Shape shape;
        if (left instanceof Point point) {
            shape = SpatialContext.GEO.getShapeFactory().pointXY(point.getX(), point.getY());
        } else if (left instanceof Double[] values) {
            shape = SpatialContext.GEO.getShapeFactory().pointXY(values[0], values[1]);
        } else if (left instanceof String str) {
            shape = GeoJSONUtils.wkt2Shape(str);
        } else {
            shape = GeoJSONUtils.map2Shape((Map<String, Object>) left);
        }
        return shape;
    }

    @SuppressWarnings("unchecked")
    private Shape parseRightShape(Object right) {
        return (right instanceof String str) ?
            GeoJSONUtils.wkt2Shape(str) :
            GeoJSONUtils.map2Shape((Map<String, Object>) right);
    }

    @Override
    public Symbol normalizeSymbol(Function symbol, TransactionContext txnCtx, NodeContext nodeCtx) {
        Symbol left = symbol.arguments().get(0);
        Symbol right = symbol.arguments().get(1);

        short numLiterals = 0;

        if (left.symbolType().isValueSymbol()) {
            numLiterals++;
        }

        if (right.symbolType().isValueSymbol()) {
            numLiterals++;
        }

        if (numLiterals == 2) {
            return Literal.of(evaluate((Input<?>) left, (Input<?>) right));
        }

        return symbol;
    }

    @Override
    public Query toQuery(Function parent, Function inner, Context context) {<FILL_FUNCTION_BODY>}

    @Override
    public Query toQuery(Reference ref, Literal<?> literal) {
        if (ref.valueType().equals(DataTypes.GEO_SHAPE)) {
            // Can only optimize on point columns, not on shapes
            return null;
        }
        Map<String, Object> geoJSON = DataTypes.GEO_SHAPE.implicitCast(literal.value());
        Geometry geometry;
        Shape shape = GeoJSONUtils.map2Shape(geoJSON);
        if (shape instanceof ShapeCollection<?> collection) {
            int i = 0;
            org.locationtech.jts.geom.Polygon[] polygons = new org.locationtech.jts.geom.Polygon[collection.size()];
            for (Shape s : collection.getShapes()) {
                Geometry subGeometry = JtsSpatialContext.GEO.getShapeFactory().getGeometryFrom(s);
                if (subGeometry instanceof org.locationtech.jts.geom.Polygon polygon) {
                    polygons[i++] = polygon;
                } else {
                    throw new InvalidShapeException("Shape collection must contain only Polygon shapes.");
                }
            }
            GeometryFactory geometryFactory = JtsSpatialContext.GEO.getShapeFactory().getGeometryFactory();
            geometry = geometryFactory.createMultiPolygon(polygons);
        } else {
            geometry = JtsSpatialContext.GEO.getShapeFactory().getGeometryFrom(shape);
        }

        return getPolygonQuery(ref.storageIdent(), geometry);
    }

    private static Query getPolygonQuery(String column, Geometry geometry) {
        Coordinate[] coordinates = geometry.getCoordinates();
        // close the polygon shape if startpoint != endpoint
        if (!CoordinateArrays.isRing(coordinates)) {
            coordinates = Arrays.copyOf(coordinates, coordinates.length + 1);
            coordinates[coordinates.length - 1] = coordinates[0];
        }

        final double[] lats = new double[coordinates.length];
        final double[] lons = new double[coordinates.length];
        for (int i = 0; i < coordinates.length; i++) {
            lats[i] = coordinates[i].y;
            lons[i] = coordinates[i].x;
        }
        return LatLonPoint.newPolygonQuery(column, new Polygon(lats, lons));
    }
}

        // within(p, pointOrShape) = [ true | false ]
        if (parent.name().equals(EqOperator.NAME)
                && parent.arguments().get(1) instanceof Literal<?> eqLiteral
                && inner.arguments().get(0) instanceof Reference ref
                && inner.arguments().get(1) instanceof Literal<?> pointOrShape) {

            Query query = toQuery(ref, pointOrShape);
            if (query == null) {
                return null;
            }
            Boolean isWithin = (Boolean) eqLiteral.value();
            if (isWithin == null) {
                // Need to fallback to generic function filter because `null = null` is `null`
                // and depending on parent queries that could turn into a match.
                return null;
            }
            return isWithin ? query : Queries.not(query);
        }
        return null;
    
<methods>public BoundSignature boundSignature() ,public Scalar<java.lang.Boolean,java.lang.Object> compile(List<io.crate.expression.symbol.Symbol>, java.lang.String, io.crate.role.Roles) ,public transient abstract java.lang.Boolean 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