diff --git a/jmeos-core/src/main/java/types/basic/tpoint/TPoint.java b/jmeos-core/src/main/java/types/basic/tpoint/TPoint.java index eaa8cc47b..80d594c90 100644 --- a/jmeos-core/src/main/java/types/basic/tpoint/TPoint.java +++ b/jmeos-core/src/main/java/types/basic/tpoint/TPoint.java @@ -288,7 +288,7 @@ default Point value_at_timestamp(LocalDateTime ts, int precision) throws ParseEx * @return A {@link Float} with the length of the trajectory. */ default float length(){ - return (float) GeneratedFunctions.tpoint_length(getPointInner()); + return (float) GeneratedFunctions.tpoint_length(getPointInner(), true); } @@ -301,7 +301,7 @@ default float length(){ * @return A {@link TFloat} with the cumulative length of the trajectory. */ default TFloat cumulative_length(){ - return (TFloat) Factory.create_temporal(GeneratedFunctions.tpoint_cumulative_length(getPointInner()),"Float",getTemporalType()); + return (TFloat) Factory.create_temporal(GeneratedFunctions.tpoint_cumulative_length(getPointInner(), true),"Float",getTemporalType()); } @@ -314,7 +314,7 @@ default TFloat cumulative_length(){ * @return A {@link TFloat} with the speed of the temporal point. */ default TFloat speed(){ - return (TFloat) Factory.create_temporal(GeneratedFunctions.tpoint_speed(getPointInner()),"Float",getTemporalType()); + return (TFloat) Factory.create_temporal(GeneratedFunctions.tpoint_speed(getPointInner(), true),"Float",getTemporalType()); } @@ -939,11 +939,11 @@ default boolean is_ever_disjoint(Object other) throws OperationNotSupportedExcep */ default boolean is_ever_within_distance(Object other, float distance) throws OperationNotSupportedException { if (other instanceof Geometry){ - return 1 == GeneratedFunctions.edwithin_tgeo_geo( getPointInner(), ConversionUtils.geo_to_gserialized((Geometry) other, this instanceof TGeogPoint), distance); + return 1 == GeneratedFunctions.edwithin_tgeo_geo( getPointInner(), ConversionUtils.geo_to_gserialized((Geometry) other, this instanceof TGeogPoint), distance, true); } else if (other instanceof STBox) { - return 1 == GeneratedFunctions.edwithin_tgeo_geo(getPointInner(), GeneratedFunctions.stbox_to_geo(((STBox) other).get_inner()), distance); + return 1 == GeneratedFunctions.edwithin_tgeo_geo(getPointInner(), GeneratedFunctions.stbox_to_geo(((STBox) other).get_inner()), distance, true); } else if (other instanceof TPoint) { - return 1 == GeneratedFunctions.edwithin_tgeo_tgeo(getPointInner(), ((TPoint) other).getPointInner(), distance); + return 1 == GeneratedFunctions.edwithin_tgeo_tgeo(getPointInner(), ((TPoint) other).getPointInner(), distance, true); } else{ throw new OperationNotSupportedException("Operand not supported"); } @@ -1058,11 +1058,11 @@ default TBool disjoint(Object other) throws OperationNotSupportedException { */ default TBool within_distance(Object other, float distance) throws OperationNotSupportedException { if (other instanceof Geometry){ - return (TBool) Factory.create_temporal(GeneratedFunctions.tdwithin_tgeo_geo(getPointInner(),ConversionUtils.geo_to_gserialized((Geometry) other, this instanceof TGeogPoint), distance), "Boolean", getTemporalType() ) ; + return (TBool) Factory.create_temporal(GeneratedFunctions.tdwithin_tgeo_geo(getPointInner(),ConversionUtils.geo_to_gserialized((Geometry) other, this instanceof TGeogPoint), distance, true), "Boolean", getTemporalType() ) ; } else if (other instanceof STBox) { - return (TBool) Factory.create_temporal(GeneratedFunctions.tdwithin_tgeo_geo(getPointInner(),GeneratedFunctions.stbox_to_geo(((STBox) other).get_inner()), distance), "Boolean", getTemporalType() ); + return (TBool) Factory.create_temporal(GeneratedFunctions.tdwithin_tgeo_geo(getPointInner(),GeneratedFunctions.stbox_to_geo(((STBox) other).get_inner()), distance, true), "Boolean", getTemporalType() ); } else if(other instanceof TPoint){ - return (TBool) Factory.create_temporal(GeneratedFunctions.tdwithin_tgeo_tgeo(getPointInner(),((TPoint) other).getPointInner(), distance), "Boolean", getTemporalType() ); + return (TBool) Factory.create_temporal(GeneratedFunctions.tdwithin_tgeo_tgeo(getPointInner(),((TPoint) other).getPointInner(), distance, true), "Boolean", getTemporalType() ); }else{ throw new OperationNotSupportedException("Operand not supported"); } @@ -1128,11 +1128,11 @@ default TBool touches(Object other) throws OperationNotSupportedException { */ default TFloat distance(Object other) throws OperationNotSupportedException { if (other instanceof Geometry){ - return (TFloat) Factory.create_temporal(GeneratedFunctions.tdistance_tgeo_geo(getPointInner(),ConversionUtils.geo_to_gserialized((Geometry) other, this instanceof TGeogPoint)), "Float", getTemporalType() ) ; + return (TFloat) Factory.create_temporal(GeneratedFunctions.tdistance_tgeo_geo(getPointInner(),ConversionUtils.geo_to_gserialized((Geometry) other, this instanceof TGeogPoint), true), "Float", getTemporalType() ) ; } else if (other instanceof STBox) { - return (TFloat) Factory.create_temporal(GeneratedFunctions.tdistance_tgeo_geo(getPointInner(),GeneratedFunctions.stbox_to_geo(((STBox) other).get_inner())), "Float", getTemporalType() ); + return (TFloat) Factory.create_temporal(GeneratedFunctions.tdistance_tgeo_geo(getPointInner(),GeneratedFunctions.stbox_to_geo(((STBox) other).get_inner()), true), "Float", getTemporalType() ); } else if(other instanceof TPoint){ - return (TFloat) Factory.create_temporal(GeneratedFunctions.tdistance_tgeo_tgeo(getPointInner(),((TPoint) other).getPointInner()), "Float", getTemporalType() ); + return (TFloat) Factory.create_temporal(GeneratedFunctions.tdistance_tgeo_tgeo(getPointInner(),((TPoint) other).getPointInner(), true), "Float", getTemporalType() ); }else{ throw new OperationNotSupportedException("Operand not supported"); } @@ -1153,11 +1153,11 @@ default TFloat distance(Object other) throws OperationNotSupportedException { */ default float nearest_approach_distance(Object other) throws OperationNotSupportedException { if (other instanceof Geometry){ - return (float) GeneratedFunctions.nad_tgeo_geo( getPointInner(), ConversionUtils.geo_to_gserialized((Geometry) other, this instanceof TGeogPoint)); + return (float) GeneratedFunctions.nad_tgeo_geo( getPointInner(), ConversionUtils.geo_to_gserialized((Geometry) other, this instanceof TGeogPoint), true); } else if (other instanceof STBox) { - return (float) GeneratedFunctions.nad_tgeo_stbox(getPointInner(), GeneratedFunctions.stbox_to_geo(((STBox) other).get_inner())); + return (float) GeneratedFunctions.nad_tgeo_stbox(getPointInner(), GeneratedFunctions.stbox_to_geo(((STBox) other).get_inner()), true); } else if (other instanceof TPoint) { - return (float) GeneratedFunctions.nad_tgeo_tgeo(getPointInner(), ((TPoint) other).getPointInner()); + return (float) GeneratedFunctions.nad_tgeo_tgeo(getPointInner(), ((TPoint) other).getPointInner(), true); } else{ throw new OperationNotSupportedException("Operand not supported"); } @@ -1177,9 +1177,9 @@ default float nearest_approach_distance(Object other) throws OperationNotSupport */ default TInstant nearest_approach_instant(Object other) throws OperationNotSupportedException { if (other instanceof Geometry){ - return (TInstant) Factory.create_temporal(GeneratedFunctions.nai_tgeo_geo(getPointInner(),ConversionUtils.geo_to_gserialized((Geometry) other, this instanceof TGeogPoint)), getCustomType(), getTemporalType() ) ; + return (TInstant) Factory.create_temporal(GeneratedFunctions.nai_tgeo_geo(getPointInner(),ConversionUtils.geo_to_gserialized((Geometry) other, this instanceof TGeogPoint), true), getCustomType(), getTemporalType() ) ; } else if(other instanceof TPoint){ - return (TInstant) Factory.create_temporal(GeneratedFunctions.nai_tgeo_tgeo(getPointInner(),((TPoint) other).getPointInner()), getCustomType(), getTemporalType() ); + return (TInstant) Factory.create_temporal(GeneratedFunctions.nai_tgeo_tgeo(getPointInner(),((TPoint) other).getPointInner(), true), getCustomType(), getTemporalType() ); }else{ throw new OperationNotSupportedException("Operand not supported"); } @@ -1204,9 +1204,9 @@ default Geometry shortest_line(Object other) throws OperationNotSupportedExcepti if (other instanceof Geometry){ boolean b= this instanceof TGeogPoint; Pointer gs= ConversionUtils.geo_to_gserialized((Geometry) other, b); - res= GeneratedFunctions.shortestline_tgeo_geo(this.getPointInner(), gs); + res= GeneratedFunctions.shortestline_tgeo_geo(this.getPointInner(), gs, true); } else if(other instanceof TPoint){ - res= GeneratedFunctions.shortestline_tgeo_geo(this.getPointInner(), ((TPoint) other).getPointInner()); + res= GeneratedFunctions.shortestline_tgeo_geo(this.getPointInner(), ((TPoint) other).getPointInner(), true); }else{ throw new OperationNotSupportedException("Operand not supported"); } diff --git a/jmeos-core/src/main/java/types/boxes/STBox.java b/jmeos-core/src/main/java/types/boxes/STBox.java index b29913df3..79dcdbd3e 100644 --- a/jmeos-core/src/main/java/types/boxes/STBox.java +++ b/jmeos-core/src/main/java/types/boxes/STBox.java @@ -1052,7 +1052,7 @@ public boolean is_over_or_after(TemporalObject other) throws Exception { * @return a Float instance with the distance between the nearest points of "this" and "``other``". */ public float nearest_approach_distance_geom(Geometry other) { - return (float) GeneratedFunctions.nad_stbox_geo(this._inner, ConversionUtils.geo_to_gserialized(other, this.geodetic())); + return (float) GeneratedFunctions.nad_stbox_geo(this._inner, ConversionUtils.geo_to_gserialized(other, this.geodetic()), true); } @@ -1070,7 +1070,7 @@ public float nearest_approach_distance_geom(Geometry other) { * @return a Float instance with the distance between the nearest points of "this" and "``other``". */ public float nearest_approach_distance_stbox(STBox other) { - return (float) GeneratedFunctions.nad_stbox_stbox(this._inner, other._inner); + return (float) GeneratedFunctions.nad_stbox_stbox(this._inner, other._inner, true); } @@ -1088,7 +1088,7 @@ public float nearest_approach_distance_stbox(STBox other) { * @return a Float instance with the distance between the nearest points of "this" and "``other``". */ public float nearest_approach_distance_tpoint(TPoint other) { - return (float) GeneratedFunctions.nad_tgeo_stbox(this._inner, other.getPointInner()); + return (float) GeneratedFunctions.nad_tgeo_stbox(this._inner, other.getPointInner(), true); } diff --git a/jmeos-core/src/main/java/types/temporal/Temporal.java b/jmeos-core/src/main/java/types/temporal/Temporal.java index 11dacf0af..dc642ba67 100644 --- a/jmeos-core/src/main/java/types/temporal/Temporal.java +++ b/jmeos-core/src/main/java/types/temporal/Temporal.java @@ -1287,7 +1287,7 @@ public boolean is_over_or_after(TemporalObject other) throws Exception { * @return A {@link Float} with the Frechet distance. */ public float frechet_distance(Temporal other){ - return (float) GeneratedFunctions.temporal_frechet_distance(this.inner,other.getInner()); + return (float) GeneratedFunctions.temporal_frechet_distance(this.inner,other.getInner(), true); } /** @@ -1302,7 +1302,7 @@ public float frechet_distance(Temporal other){ * @return A {@link Float} with the Dynamic Time Warp distance. */ public float dyntimewarp_distance(Temporal other){ - return (float) GeneratedFunctions.temporal_dyntimewarp_distance(this.inner,other.getInner()); + return (float) GeneratedFunctions.temporal_dyntimewarp_distance(this.inner,other.getInner(), true); } /** @@ -1317,7 +1317,7 @@ public float dyntimewarp_distance(Temporal other){ * @return A {@link Float} with the Hausdorff distance. */ public float hausdorff_distance(Temporal other){ - return (float) GeneratedFunctions.temporal_hausdorff_distance(this.inner,other.getInner()); + return (float) GeneratedFunctions.temporal_hausdorff_distance(this.inner,other.getInner(), true); } diff --git a/jmeos-core/src/test/java/types/temporal/generated/GeneratedTemporalParityTest.java b/jmeos-core/src/test/java/types/temporal/generated/GeneratedTemporalParityTest.java index e1962ff80..3f7a46360 100644 --- a/jmeos-core/src/test/java/types/temporal/generated/GeneratedTemporalParityTest.java +++ b/jmeos-core/src/test/java/types/temporal/generated/GeneratedTemporalParityTest.java @@ -208,7 +208,7 @@ void objectArgumentsMatchTheLibrary() { // A temporal object argument is forwarded as its inner pointer. assertEquals(id(GeneratedFunctions.temporal_merge(pa, b.getInner())), id(g.merge(b))); - assertEquals(GeneratedFunctions.temporal_frechet_distance(pa, b.getInner()), g.frechetDistance(b)); + assertEquals(GeneratedFunctions.temporal_frechet_distance(pa, b.getInner(), true), g.frechetDistance(b, true)); assertEquals(id(GeneratedFunctions.temporal_insert(pa, b.getInner(), true)), id(g.insert(b, true))); assertEquals(id(GeneratedFunctions.temporal_append_tsequence(pa, b.getInner(), false)), id(g.appendTsequence(b, false))); @@ -400,8 +400,8 @@ void similarityPathsMatchTheLibrary() { // frechetPath folds the Match array into records; compare each to the raw {i, j} pair. Pointer c = Memory.allocate(rt, Integer.BYTES); - Pointer arr = GeneratedFunctions.temporal_frechet_path(pa, b.getInner(), c); - List path = g.frechetPath(b); + Pointer arr = GeneratedFunctions.temporal_frechet_path(pa, b.getInner(), true, c); + List path = g.frechetPath(b, true); assertEquals(c.getInt(0), path.size()); for (int i = 0; i < path.size(); i++) { assertEquals(arr.getInt((long) i * 8), path.get(i).i()); @@ -410,8 +410,8 @@ void similarityPathsMatchTheLibrary() { // The dynamic-time-warp path folds the same way. Pointer c2 = Memory.allocate(rt, Integer.BYTES); - GeneratedFunctions.temporal_dyntimewarp_path(pa, b.getInner(), c2); - assertEquals(c2.getInt(0), g.dyntimewarpPath(b).size()); + GeneratedFunctions.temporal_dyntimewarp_path(pa, b.getInner(), true, c2); + assertEquals(c2.getInt(0), g.dyntimewarpPath(b, true).size()); } @Test diff --git a/tools/codegen_jvm.py b/tools/codegen_jvm.py index 3bcd626aa..fbba24cf8 100644 --- a/tools/codegen_jvm.py +++ b/tools/codegen_jvm.py @@ -214,8 +214,9 @@ def folded_nxn(r): else: periods = nm i += 1; continue - if c in ('double', 'int'): - scalars.append((nm, 'double' if c == 'double' else 'int')); i += 1; continue + if c in ('double', 'int', 'bool'): + scalars.append((nm, {'double': 'double', 'int': 'int', 'bool': 'boolean'}[c])) + i += 1; continue return None if not arrays or count is None: return None diff --git a/tools/codegen_spark_udfs.py b/tools/codegen_spark_udfs.py index 51ebb7a85..a9ae710f9 100644 --- a/tools/codegen_spark_udfs.py +++ b/tools/codegen_spark_udfs.py @@ -760,7 +760,8 @@ def emit_timearg(name, op): def tgeoarr_shape(f): """Recognize a MEOS NxN array kernel — params are one or more (Temporal **, int) - array pairs, an optional `double` (distance), and optional non-const out-params + array pairs, an optional `double` (distance), an optional `bool` (the model of the earth + a geography is measured on, `spheroid`), and optional non-const out-params (int *count, SpanSet ***periods). Returns a shape dict or None. ret: 'double' (scalar, e.g. minDistance) | 'pairs' (int* of 2*count [i,j], e.g. the *Pairs functions). periods=True iff a SpanSet*** out-param is present (tDwithin).""" @@ -768,6 +769,7 @@ def tgeoarr_shape(f): n = len(ps) i = arrays = 0 dist = has_count = periods = False + flag = None while i < n: c = norm(ps[i]["canonical"]); isconst = "const" in ps[i]["canonical"] bare = c.replace("*", "").strip() # base() maps ** -> __INTERNAL__, so strip here @@ -777,6 +779,8 @@ def tgeoarr_shape(f): return None if c == "double": dist = True; i += 1; continue + if c == "bool" and flag is None: + flag = ps[i]["name"]; i += 1; continue if c == "int *" and not isconst: has_count = True; i += 1; continue if bare == "SpanSet" and c.count("*") == 3 and not isconst: @@ -788,7 +792,7 @@ def tgeoarr_shape(f): ret = "double" if rt == "double" else ("pairs" if rt == "int *" else None) if ret is None or (ret == "pairs" and not has_count): return None - return {"arrays": arrays, "dist": dist, "periods": periods, "ret": ret} + return {"arrays": arrays, "dist": dist, "flag": flag, "periods": periods, "ret": ret} def emit_tgeoarr(name, f, shape): @@ -798,10 +802,19 @@ def emit_tgeoarr(name, f, shape): yield a Double UDF; pairs-return kernels yield array> (consumed via LATERAL explode), with MEOS 1-based indices mapped to 0-based.""" nA = shape["arrays"] - argnames = ["a%d" % i for i in range(nA)] + (["dist"] if shape["dist"] else []) + # the flag a SQL signature omits (its SQL-required arity stops before it) is the value + # MobilityDB gives it, as #emit_single supplies a hidden flag + flag, hidden = shape["flag"], None + vis = nA + (1 if shape["dist"] else 0) + if flag and f.get("sqlArity") == vis: + p = next(q for q in f["params"] if q["name"] == flag) + flag, hidden = None, hidden_arg(f, p, vis, name) + argnames = ["a%d" % i for i in range(nA)] + (["dist"] if shape["dist"] else []) \ + + ([flag] if flag else []) # dist arrives as Object: a bare SQL literal like 10.0 is decimal (BigDecimal), not # double, so take it as Object and coerce via Number rather than failing the cast. - boxes = ["Object"] * nA + (["Object"] if shape["dist"] else []) + boxes = ["Object"] * nA + (["Object"] if shape["dist"] else []) \ + + (["Boolean"] if flag else []) if shape["ret"] == "double": retbox, ret_dt = "Double", "DataTypes.DoubleType" else: @@ -813,7 +826,9 @@ def emit_tgeoarr(name, f, shape): retbox, ret_dt = "java.util.List", "DataTypes.createArrayType(%s)" % struct iface = "UDF%d<%s, %s>" % (len(argnames), ", ".join(boxes), retbox) L = [' spark.udf().register("%s", (%s) (%s) -> {' % (name, iface, ", ".join(argnames))] - L.append(" if (" + " || ".join("a%d == null" % i for i in range(nA)) + ") return null;") + L.append(" if (" + " || ".join(["a%d == null" % i for i in range(nA)] + + ([flag + " == null"] if flag else [])) + + ") return null;") for i in range(nA): L.append(" Object[] s%d = UdfMarshal.asArray(a%d);" % (i, i)) L.append(" if (" + " || ".join("s%d == null" % i for i in range(nA)) + ") return null;") @@ -826,6 +841,10 @@ def emit_tgeoarr(name, f, shape): callargs += ["arr%d" % i, "s%d.length" % i] if shape["dist"]: callargs.append("dist == null ? 0.0 : ((Number) dist).doubleValue()") + if flag: + callargs.append(flag) + elif hidden: + callargs.append(hidden) if shape["ret"] == "pairs": L.append(" jnr.ffi.Pointer _cnt = jnr.ffi.Memory.allocateDirect(_rt, 4);") callargs.append("_cnt") @@ -1050,13 +1069,22 @@ def row_column(f, col): return None -def setret_shape(f, sig): +def setret_shape(f, sig, vis=None): """The rows a set-returning signature of `f` returns, or None when they cannot be read: the inputs (every parameter outside shape.outParams, each marshallable), the `int *count` - out-parameter, the out-parameters holding arrays, and one reader per column.""" + out-parameter, the out-parameters holding arrays, and one reader per column. With `vis`, the + SQL-required arity of a SQL name, the inputs from `vis` on are hidden when each is a + defaultable flag preceding every out-parameter, as #emit_single hides them.""" outs = f.get("shape", {}).get("outParams", []) params = f["params"] ins = [p for p in params if p["name"] not in outs] + hidden = [] + if vis is not None and 0 < vis < len(ins): + tail = ins[vis:] + first_out = min([i for i, q in enumerate(params) if q["name"] in outs] or [len(params)]) + if all(base(q["canonical"]) in HIDE_DEFAULT and "*" not in norm(q["canonical"]) + and params.index(q) < first_out for q in tail): + hidden, ins = tail, ins[:vis] counts = [p for p in params if p["name"] in outs and norm(p["canonical"]) == "int *" and "const" not in p["canonical"]] if len(counts) != 1 or not ins or any(arg_kind(p["canonical"]) is None for p in ins): @@ -1071,7 +1099,8 @@ def setret_shape(f, sig): arrays = [p["name"] for p in params if p["name"] in outs and p is not counts[0]] if any(r["src"] not in ("return", "ordinal") and r["src"] not in arrays for r in readers): return None - return {"ins": ins, "count": counts[0]["name"], "arrays": arrays, "cols": readers} + return {"ins": ins, "hidden": hidden, "count": counts[0]["name"], "arrays": arrays, + "cols": readers} def _row_dt(shape): @@ -1131,6 +1160,8 @@ def emit_setret(name, cands, colnames): callargs.append("UdfMarshal.tsOdt(%s)" % a) else: callargs.append(k[3] % a) + for p in shape.get("hidden", []): + callargs.append(hidden_arg(f, p, len(shape["ins"]), name)) L.append(" if (%s) {" % (" && ".join("%s != null" % p for p in ptrs) or "true")) L.append(" jnr.ffi.Runtime _rt = jnr.ffi.Runtime.getSystemRuntime();") cells = {} @@ -1839,7 +1870,7 @@ def main(): # one first. A SQL name whose overloads need different signatures (valueSplit's int, # double and bigint bins) stays unregistered, each overload reachable under its C name. STRUCTS.update({s["name"]: s for s in cat.get("structs") or []}) - setret, nset_c, nset_sql = {}, 0, 0 + setret, setret_sql, nset_c, nset_sql = {}, {}, 0, 0 for f in fns: nm = f["name"] if jar_syms is not None and nm not in jar_syms: @@ -1854,6 +1885,8 @@ def main(): shape = setret_shape(f, sig) if shape is not None: setret.setdefault(sig.get("sqlName") or f.get("sqlfn"), []).append((f, shape)) + setret_sql.setdefault(sig.get("sqlName") or f.get("sqlfn"), []).append( + (f, setret_shape(f, sig, f.get("sqlArity")))) by_fn = {} for sname, cands in setret.items(): for f, shape in cands: @@ -1869,17 +1902,17 @@ def main(): cov += 1 nset_c += 1 set_left = [] - for sname in sorted(n for n in setret if n): + for sname in sorted(n for n in setret_sql if n): if sname in names: continue - if len({_setret_key(s) for _, s in setret[sname]}) != 1: + if len({_setret_key(s) for _, s in setret_sql[sname]}) != 1: set_left.append(sname) continue one = {} - for f, shape in setret[sname]: + for f, shape in setret_sql[sname]: one.setdefault(f["name"], (f, shape)) cols = collections.Counter(tuple(str(c["name"]) for c in s["cols"]) - for _, s in setret[sname]) + for _, s in setret_sql[sname]) grouped.setdefault("GeneratedUdfs_sqlfn", []).append( emit_setret(sname, list(one.values()), min(cols, key=lambda k: (-cols[k], k)))) names.add(sname)