diff --git a/xtraplatform-cql/src/main/java/de/ii/xtraplatform/cql/domain/CqlVisitorCopy.java b/xtraplatform-cql/src/main/java/de/ii/xtraplatform/cql/domain/CqlVisitorCopy.java index 4c27154bd..3a5aa5d75 100644 --- a/xtraplatform-cql/src/main/java/de/ii/xtraplatform/cql/domain/CqlVisitorCopy.java +++ b/xtraplatform-cql/src/main/java/de/ii/xtraplatform/cql/domain/CqlVisitorCopy.java @@ -225,7 +225,9 @@ public CqlNode visit(Bbox bbox, List children) { @Override public CqlNode visit(Function function, List children) { - return function; + return Function.of( + function.getName(), + children.stream().map(Operand.class::cast).collect(Collectors.toList())); } @Override diff --git a/xtraplatform-cql/src/main/java/de/ii/xtraplatform/cql/domain/Function.java b/xtraplatform-cql/src/main/java/de/ii/xtraplatform/cql/domain/Function.java index 81a43ac79..dbf38d6e1 100644 --- a/xtraplatform-cql/src/main/java/de/ii/xtraplatform/cql/domain/Function.java +++ b/xtraplatform-cql/src/main/java/de/ii/xtraplatform/cql/domain/Function.java @@ -61,11 +61,7 @@ public void serialize(Function value, JsonGenerator gen, SerializerProvider seri gen.writeFieldName("args"); gen.writeStartArray(); for (Operand operand : value.getArgs()) { - if (operand instanceof ScalarLiteral) { - gen.writeString(String.format("%s", ((ScalarLiteral) operand).getValue().toString())); - } else { - gen.writeObject(operand); - } + gen.writeObject(operand); } gen.writeEndArray(); gen.writeEndObject(); diff --git a/xtraplatform-cql/src/test/groovy/de/ii/xtraplatform/cql/app/CqlFunctionArgumentsSpec.groovy b/xtraplatform-cql/src/test/groovy/de/ii/xtraplatform/cql/app/CqlFunctionArgumentsSpec.groovy new file mode 100644 index 000000000..d82d5d6bc --- /dev/null +++ b/xtraplatform-cql/src/test/groovy/de/ii/xtraplatform/cql/app/CqlFunctionArgumentsSpec.groovy @@ -0,0 +1,91 @@ +/* + * Copyright 2026 interactive instruments GmbH + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +package de.ii.xtraplatform.cql.app + +import de.ii.xtraplatform.cql.domain.Cql +import de.ii.xtraplatform.cql.domain.Cql2Expression +import de.ii.xtraplatform.cql.domain.CqlNode +import de.ii.xtraplatform.cql.domain.CqlVisitorCopy +import de.ii.xtraplatform.cql.domain.Eq +import de.ii.xtraplatform.cql.domain.Function +import de.ii.xtraplatform.cql.domain.Parameter +import de.ii.xtraplatform.cql.domain.Property +import de.ii.xtraplatform.cql.domain.ScalarLiteral +import de.ii.xtraplatform.jsonschema.domain.ImmutableJsonSchemaNumber +import spock.lang.Shared +import spock.lang.Specification + +class CqlFunctionArgumentsSpec extends Specification { + + @Shared + Cql cql + + def setupSpec() { + cql = new CqlImpl() + } + + def 'numeric arguments of a function in CQL2 JSON are numbers'() { + + given: + String cqlJson = """ + { + "op": "=", + "args": [ + {"function": {"name": "dwithin", "args": [{"property": "geometrie"}, ${literal}]}}, + true + ] + } + """ + + when: + Cql2Expression actual = cql.read(cqlJson, Cql.Format.JSON) + def argument = ((Function) ((Eq) actual).getArgs().get(0)).getArgs().get(1) + + then: + argument == expected + + where: + literal || expected + "300" || ScalarLiteral.of(300) + "300.5" || ScalarLiteral.of(300.5d) + } + + def 'literal arguments of a function keep their type when written to and read from CQL2 JSON'() { + + given: + def filter = Eq.ofFunction(Function.of("dwithin", [Property.of("geometrie"), literal]), ScalarLiteral.of(true)) + + when: + Cql2Expression actual = cql.read(cql.write(filter, Cql.Format.JSON), Cql.Format.JSON) + + then: + actual == filter + + where: + literal << [ScalarLiteral.of(300), ScalarLiteral.of(300.5d), ScalarLiteral.of("300")] + } + + def 'a copying visitor replaces parameters in the arguments of a function'() { + + given: + def parameter = Parameter.of("afstand", new ImmutableJsonSchemaNumber.Builder().build()) + def filter = Eq.ofFunction(Function.of("dwithin", [Property.of("geometrie"), parameter]), ScalarLiteral.of(true)) + def resolver = new CqlVisitorCopy() { + @Override + CqlNode visit(Parameter p, List children) { + return ScalarLiteral.of(300) + } + } + + when: + def actual = filter.accept(resolver) + + then: + actual == Eq.ofFunction(Function.of("dwithin", [Property.of("geometrie"), ScalarLiteral.of(300)]), ScalarLiteral.of(true)) + } +}