diff --git a/geaflow/geaflow-dsl/geaflow-dsl-plan/src/main/java/org/apache/geaflow/dsl/schema/function/BuildInSqlFunctionTable.java b/geaflow/geaflow-dsl/geaflow-dsl-plan/src/main/java/org/apache/geaflow/dsl/schema/function/BuildInSqlFunctionTable.java index 47addc84a..4de0a1ae5 100644 --- a/geaflow/geaflow-dsl/geaflow-dsl-plan/src/main/java/org/apache/geaflow/dsl/schema/function/BuildInSqlFunctionTable.java +++ b/geaflow/geaflow-dsl/geaflow-dsl-plan/src/main/java/org/apache/geaflow/dsl/schema/function/BuildInSqlFunctionTable.java @@ -82,9 +82,12 @@ import org.apache.geaflow.dsl.udf.table.date.WeekDay; import org.apache.geaflow.dsl.udf.table.date.WeekOfYear; import org.apache.geaflow.dsl.udf.table.date.Year; +import org.apache.geaflow.dsl.udf.table.math.Cbrt; import org.apache.geaflow.dsl.udf.table.math.E; import org.apache.geaflow.dsl.udf.table.math.Log2; import org.apache.geaflow.dsl.udf.table.math.Round; +import org.apache.geaflow.dsl.udf.table.math.Sign; +import org.apache.geaflow.dsl.udf.table.math.Trunc; import org.apache.geaflow.dsl.udf.table.other.Direction; import org.apache.geaflow.dsl.udf.table.other.EdgeSrcId; import org.apache.geaflow.dsl.udf.table.other.EdgeTargetId; @@ -169,9 +172,12 @@ public class BuildInSqlFunctionTable extends ListSqlOperatorTable { .add(GeaFlowFunction.of(ArrayUnion.class)) // udf.table.math + .add(GeaFlowFunction.of(Cbrt.class)) .add(GeaFlowFunction.of(E.class)) .add(GeaFlowFunction.of(Log2.class)) .add(GeaFlowFunction.of(Round.class)) + .add(GeaFlowFunction.of(Sign.class)) + .add(GeaFlowFunction.of(Trunc.class)) // udf.table.string .add(GeaFlowFunction.of(Ascii2String.class)) diff --git a/geaflow/geaflow-dsl/geaflow-dsl-plan/src/main/java/org/apache/geaflow/dsl/schema/function/GeaFlowBuiltinFunctions.java b/geaflow/geaflow-dsl/geaflow-dsl-plan/src/main/java/org/apache/geaflow/dsl/schema/function/GeaFlowBuiltinFunctions.java index 3c5e669a7..5ab558dfd 100644 --- a/geaflow/geaflow-dsl/geaflow-dsl-plan/src/main/java/org/apache/geaflow/dsl/schema/function/GeaFlowBuiltinFunctions.java +++ b/geaflow/geaflow-dsl/geaflow-dsl-plan/src/main/java/org/apache/geaflow/dsl/schema/function/GeaFlowBuiltinFunctions.java @@ -1319,6 +1319,26 @@ public static Double round(Double a, Integer n) { } } + public static Double cbrt(Double a) { + if (a == null) { + return null; + } + return Math.cbrt(a); + } + + public static Double trunc(Double a, Integer n) { + if (a == null || n == null) { + return null; + } + + if (Double.isNaN(a) || Double.isInfinite(a)) { + return a; + } else { + return BigDecimal.valueOf(a).setScale(n, RoundingMode.DOWN) + .doubleValue(); + } + } + public static Boolean equal(Long a, Long b) { if (a == null || b == null) { return null; diff --git a/geaflow/geaflow-dsl/geaflow-dsl-plan/src/main/java/org/apache/geaflow/dsl/udf/table/math/Cbrt.java b/geaflow/geaflow-dsl/geaflow-dsl-plan/src/main/java/org/apache/geaflow/dsl/udf/table/math/Cbrt.java new file mode 100644 index 000000000..4394381ce --- /dev/null +++ b/geaflow/geaflow-dsl/geaflow-dsl-plan/src/main/java/org/apache/geaflow/dsl/udf/table/math/Cbrt.java @@ -0,0 +1,34 @@ +/* + * 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.geaflow.dsl.udf.table.math; + +import org.apache.geaflow.dsl.common.function.Description; +import org.apache.geaflow.dsl.common.function.UDF; + +@Description(name = "cbrt", description = "Returns the cube root of the given value.") +public class Cbrt extends UDF { + + public Double eval(Double a) { + if (a == null) { + return null; + } + return Math.cbrt(a); + } +} diff --git a/geaflow/geaflow-dsl/geaflow-dsl-plan/src/main/java/org/apache/geaflow/dsl/udf/table/math/Sign.java b/geaflow/geaflow-dsl/geaflow-dsl-plan/src/main/java/org/apache/geaflow/dsl/udf/table/math/Sign.java new file mode 100644 index 000000000..af1ed4bb6 --- /dev/null +++ b/geaflow/geaflow-dsl/geaflow-dsl-plan/src/main/java/org/apache/geaflow/dsl/udf/table/math/Sign.java @@ -0,0 +1,60 @@ +/* + * 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.geaflow.dsl.udf.table.math; + +import org.apache.geaflow.dsl.common.function.Description; +import org.apache.geaflow.dsl.common.function.UDF; + +@Description(name = "sign", description = "Returns the sign of the given value.") +public class Sign extends UDF { + + public Double eval(Double a) { + if (a == null) { + return null; + } + return Math.signum(a); + } + + public Long eval(Long n) { + if (n == null) { + return null; + } + if (n > 0) { + return 1L; + } else if (n < 0) { + return -1L; + } else { + return 0L; + } + } + + public Integer eval(Integer n) { + if (n == null) { + return null; + } + if (n > 0) { + return 1; + } else if (n < 0) { + return -1; + } else { + return 0; + } + } +} diff --git a/geaflow/geaflow-dsl/geaflow-dsl-plan/src/main/java/org/apache/geaflow/dsl/udf/table/math/Trunc.java b/geaflow/geaflow-dsl/geaflow-dsl-plan/src/main/java/org/apache/geaflow/dsl/udf/table/math/Trunc.java new file mode 100644 index 000000000..c8dcfdcd8 --- /dev/null +++ b/geaflow/geaflow-dsl/geaflow-dsl-plan/src/main/java/org/apache/geaflow/dsl/udf/table/math/Trunc.java @@ -0,0 +1,67 @@ +/* + * 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.geaflow.dsl.udf.table.math; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import org.apache.geaflow.dsl.common.function.Description; +import org.apache.geaflow.dsl.common.function.UDF; + +@Description(name = "trunc", description = "Truncates x to d decimal places") +public class Trunc extends UDF { + + private Double eval(Double n, int i) { + double d = n; + if (Double.isNaN(d) || Double.isInfinite(d)) { + return d; + } else { + return BigDecimal.valueOf(d).setScale(i, RoundingMode.DOWN).doubleValue(); + } + } + + public Double eval(Double n) { + if (n == null) { + return null; + } + return eval(n, 0); + } + + public Long eval(Long n) { + return n; + } + + public Integer eval(Integer n) { + return n; + } + + public Double eval(Double n, Long i) { + if ((n == null) || (i == null)) { + return null; + } + return eval(n, i.intValue()); + } + + public Double eval(Double n, Integer i) { + if ((n == null) || (i == null)) { + return null; + } + return eval(n, i.intValue()); + } +} diff --git a/geaflow/geaflow-dsl/geaflow-dsl-plan/src/test/java/org/apache/geaflow/dsl/schema/InternalFunctionsTest.java b/geaflow/geaflow-dsl/geaflow-dsl-plan/src/test/java/org/apache/geaflow/dsl/schema/InternalFunctionsTest.java index 084e4e5af..905e45d63 100644 --- a/geaflow/geaflow-dsl/geaflow-dsl-plan/src/test/java/org/apache/geaflow/dsl/schema/InternalFunctionsTest.java +++ b/geaflow/geaflow-dsl/geaflow-dsl-plan/src/test/java/org/apache/geaflow/dsl/schema/InternalFunctionsTest.java @@ -25,7 +25,9 @@ import static org.apache.geaflow.dsl.schema.function.GeaFlowBuiltinFunctions.atan; import static org.apache.geaflow.dsl.schema.function.GeaFlowBuiltinFunctions.ceil; import static org.apache.geaflow.dsl.schema.function.GeaFlowBuiltinFunctions.cos; +import static org.apache.geaflow.dsl.schema.function.GeaFlowBuiltinFunctions.cbrt; import static org.apache.geaflow.dsl.schema.function.GeaFlowBuiltinFunctions.cot; +import static org.apache.geaflow.dsl.schema.function.GeaFlowBuiltinFunctions.trunc; import static org.apache.geaflow.dsl.schema.function.GeaFlowBuiltinFunctions.degrees; import static org.apache.geaflow.dsl.schema.function.GeaFlowBuiltinFunctions.divide; import static org.apache.geaflow.dsl.schema.function.GeaFlowBuiltinFunctions.equal; @@ -645,6 +647,24 @@ public void testOtherFunction() { Assert.assertNull(round(doubleNull, 2)); } + @Test + public void testCbrt() { + assertEquals(cbrt(8.0), 2.0); + assertEquals(cbrt(27.0), 3.0); + assertEquals(cbrt(-8.0), -2.0); + assertEquals(cbrt(0.0), 0.0); + Assert.assertNull(cbrt(doubleNull)); + } + + @Test + public void testTrunc() { + assertEquals(trunc(3.567, 2), 3.56); + assertEquals(trunc(3.567, 0), 3.0); + assertEquals(trunc(-3.567, 2), -3.56); + Assert.assertNull(trunc(doubleNull, 2)); + Assert.assertNull(trunc(3.567, intNull)); + } + @Test public void testGeaFlowUserDefinedTableFunction() { GQLJavaTypeFactory typeFactory = GQLJavaTypeFactory.create(); diff --git a/geaflow/geaflow-dsl/geaflow-dsl-plan/src/test/java/org/apache/geaflow/dsl/udf/math/MathUdfTest.java b/geaflow/geaflow-dsl/geaflow-dsl-plan/src/test/java/org/apache/geaflow/dsl/udf/math/MathUdfTest.java new file mode 100644 index 000000000..d579fc911 --- /dev/null +++ b/geaflow/geaflow-dsl/geaflow-dsl-plan/src/test/java/org/apache/geaflow/dsl/udf/math/MathUdfTest.java @@ -0,0 +1,83 @@ +/* + * 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.geaflow.dsl.udf.math; + +import org.apache.geaflow.dsl.udf.table.math.Cbrt; +import org.apache.geaflow.dsl.udf.table.math.Sign; +import org.apache.geaflow.dsl.udf.table.math.Trunc; +import org.testng.Assert; +import org.testng.annotations.Test; + +public class MathUdfTest { + + @Test + public void testSign() { + Sign sign = new Sign(); + // Double + Assert.assertEquals(sign.eval(3.14), 1.0); + Assert.assertEquals(sign.eval(-3.14), -1.0); + Assert.assertEquals(sign.eval(0.0), 0.0); + Assert.assertNull(sign.eval((Double) null)); + // Long + Assert.assertEquals(sign.eval(5L), Long.valueOf(1L)); + Assert.assertEquals(sign.eval(-5L), Long.valueOf(-1L)); + Assert.assertEquals(sign.eval(0L), Long.valueOf(0L)); + Assert.assertNull(sign.eval((Long) null)); + // Integer + Assert.assertEquals(sign.eval(5), Integer.valueOf(1)); + Assert.assertEquals(sign.eval(-5), Integer.valueOf(-1)); + Assert.assertEquals(sign.eval(0), Integer.valueOf(0)); + Assert.assertNull(sign.eval((Integer) null)); + } + + @Test + public void testCbrt() { + Cbrt cbrt = new Cbrt(); + Assert.assertEquals(cbrt.eval(27.0), 3.0); + Assert.assertEquals(cbrt.eval(-8.0), -2.0); + Assert.assertEquals(cbrt.eval(0.0), 0.0); + Assert.assertEquals(cbrt.eval(1.0), 1.0); + Assert.assertNull(cbrt.eval(null)); + } + + @Test + public void testTrunc() { + Trunc trunc = new Trunc(); + // Trunc vs Round: trunc(3.1465, 2) = 3.14, round(3.1465, 2) = 3.15 + Assert.assertEquals(trunc.eval(3.1465, 2L), 3.14); + Assert.assertEquals(trunc.eval(3.1415, 2L), 3.14); + // Negative: DOWN mode truncates toward zero + Assert.assertEquals(trunc.eval(-3.1465, 2L), -3.14); + // Default: truncate to 0 decimal places + Assert.assertEquals(trunc.eval(3.1415), 3.0); + Assert.assertEquals(trunc.eval(-3.9), -3.0); + // Integer scale + Assert.assertEquals(trunc.eval(3.1465, 2), 3.14); + // Null handling + Assert.assertNull(trunc.eval(null, 2L)); + Assert.assertNull(trunc.eval(3.14, (Long) null)); + Assert.assertNull(trunc.eval(null, 2)); + Assert.assertNull(trunc.eval(3.14, (Integer) null)); + Assert.assertNull(trunc.eval((Double) null)); + // Long/Integer pass-through + Assert.assertEquals(trunc.eval(5L), Long.valueOf(5L)); + Assert.assertEquals(trunc.eval(5), Integer.valueOf(5)); + } +} diff --git a/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/java/org/apache/geaflow/dsl/runtime/query/udf/MathSignCbrtTruncTest.java b/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/java/org/apache/geaflow/dsl/runtime/query/udf/MathSignCbrtTruncTest.java new file mode 100644 index 000000000..3c7a7908a --- /dev/null +++ b/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/java/org/apache/geaflow/dsl/runtime/query/udf/MathSignCbrtTruncTest.java @@ -0,0 +1,44 @@ +/* + * 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.geaflow.dsl.runtime.query.udf; + +import org.apache.geaflow.dsl.runtime.query.QueryTester; +import org.testng.annotations.Test; + +/** + * End-to-end SQL tests for the SIGN, CBRT, and TRUNC math UDFs. + * + *
Unlike the unit tests in {@code MathUdfTest} which invoke the Java + * {@code eval()} methods directly, these tests exercise the full SQL + * pipeline: function registration in {@code BuildInSqlFunctionTable}, + * overload resolution, type inference, and runtime invocation through + * the GeaFlow query engine.
+ */ +public class MathSignCbrtTruncTest { + + @Test + public void testSignCbrtTrunc() throws Exception { + QueryTester + .build() + .withQueryPath("/query/math_sign_cbrt_trunc_001.sql") + .execute() + .checkSinkResult(); + } +} diff --git a/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/resources/data/math_sign_cbrt_trunc.txt b/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/resources/data/math_sign_cbrt_trunc.txt new file mode 100644 index 000000000..504a14482 --- /dev/null +++ b/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/resources/data/math_sign_cbrt_trunc.txt @@ -0,0 +1,7 @@ +1|3.14|5|7 +2|-2.71|-8|-3 +3|0.0|0|0 +4|27.0|100|64 +5|-8.0|-27|-8 +6|3.1465|999|999 +7|-3.1465|-999|-999 diff --git a/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/resources/expect/math_sign_cbrt_trunc_001.txt b/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/resources/expect/math_sign_cbrt_trunc_001.txt new file mode 100644 index 000000000..214ed55fd --- /dev/null +++ b/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/resources/expect/math_sign_cbrt_trunc_001.txt @@ -0,0 +1,7 @@ +1|1.0|1.0|1.0|1.4643443505031195|3.14|3.0|5|7 +2|-1.0|-1.0|-1.0|-1.3941936390611858|-2.71|-2.0|-8|-3 +3|0.0|0.0|0.0|0.0|0.0|0.0|0|0 +4|1.0|1.0|1.0|3.0|27.0|27.0|100|64 +5|-1.0|-1.0|-1.0|-2.0|-8.0|-8.0|-27|-8 +6|1.0|1.0|1.0|1.4653540827785496|3.14|3.0|999|999 +7|-1.0|-1.0|-1.0|-1.4653540827785496|-3.14|-3.0|-999|-999 diff --git a/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/resources/query/math_sign_cbrt_trunc_001.sql b/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/resources/query/math_sign_cbrt_trunc_001.sql new file mode 100644 index 000000000..4ac07332b --- /dev/null +++ b/geaflow/geaflow-dsl/geaflow-dsl-runtime/src/test/resources/query/math_sign_cbrt_trunc_001.sql @@ -0,0 +1,83 @@ +-- 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. + +-- End-to-end SQL test for the SIGN / CBRT / TRUNC built-in math UDFs. +-- +-- Unlike the unit tests in MathUdfTest which invoke the Java eval() +-- methods directly, this exercises the full SQL pipeline: function +-- registration in BuildInSqlFunctionTable, overload resolution, type +-- inference, and runtime invocation through the query engine. +-- +-- Type-inference behaviour captured by the expected output: +-- * sign(double) -> Double (sign_d column) +-- * sign(bigint) -> Double (sign_l column) - bigint is promoted to +-- double and matches sign(Double); the Long overload is not selected +-- * sign(int) -> Double (sign_i column) - same promotion +-- * trunc(bigint) -> Long (trunc_l column) - Long overload selected +-- * trunc(int) -> Integer (trunc_i column) - Integer overload selected +-- This difference between sign() and trunc() is the real Calcite overload +-- resolution behaviour, which only an end-to-end SQL test can surface. + +set geaflow.dsl.column.separator = '|'; + +CREATE TABLE source ( + id bigint, + d_val double, + l_val bigint, + i_val int +) WITH ( + type='file', + geaflow.dsl.file.path = 'resource:///data/math_sign_cbrt_trunc.txt' +); + +CREATE TABLE tbl_result ( + id bigint, + -- SIGN overloads (Double, Long, Integer) + sign_d double, + sign_l bigint, + sign_i int, + -- CBRT (Double) + cbrt_v double, + -- TRUNC overloads: trunc(d, scale), trunc(d), trunc(long), trunc(int) + trunc_d_scale double, + trunc_d_only double, + trunc_l bigint, + trunc_i int +) WITH ( + type='file', + -- Use a relative forward-slash path instead of the ${target} placeholder. + -- QueryTester rewrites ${target} to an absolute path that on Windows + -- contains backslashes; injected into a SQL string literal those + -- backslashes get escaped (\t -> tab, \U/\s/\g dropped) and the sink + -- path is corrupted. A relative path avoids the rewrite entirely and + -- resolves to the same target/ directory checkSinkResult() reads. + geaflow.dsl.file.path='target/math_sign_cbrt_trunc_001' +); + +INSERT INTO tbl_result +SELECT + id, + sign(d_val) AS sign_d, + sign(l_val) AS sign_l, + sign(i_val) AS sign_i, + cbrt(d_val) AS cbrt_v, + trunc(d_val, 2) AS trunc_d_scale, + trunc(d_val) AS trunc_d_only, + trunc(l_val) AS trunc_l, + trunc(i_val) AS trunc_i +FROM source +ORDER BY id;