From 4e2bf3b32b120f62e3015f13a4d4c25f7922ac5b Mon Sep 17 00:00:00 2001 From: Esteban Zimanyi Date: Fri, 9 Oct 2026 15:57:29 +0200 Subject: [PATCH] Pass the model of the earth MEOS measures a geography on The MEOS functions measuring a temporal geo take a last bool spheroid, read for geographies only. The classes of jmeos-core (TPoint length, cumulative_length, speed, the ever and temporal dwithin, the temporal distance, the nearest approach distance and instant and the shortest line, the STBox nearest approach distances, the Frechet, dynamic time warping and Hausdorff distances of Temporal) pass true, the DEFAULT of the SQL argument, so each answers on the spheroid as it did. The generators carry it through the array kernels and the set-returning functions. The NxN kernels take a bool beside their distance: codegen_spark_udfs.py reads it as a Boolean argument, and the folded form of the MeosOps facade as a boolean. A UDF registered under a SQL name takes the arguments the SQL signature requires (sqlArity) and passes the value MobilityDB gives the flags it omits, as emit_single does: minDistance, eDwithinPairs, aDwithinPairs and tDwithinPairs over arrays, and dynTimeWarpPath and frechetPath, take their arguments without spheroid and pass true, while the C-named UDFs take spheroid. Witness: over MobilityDB 8a9bc20b62, where #3005 adds spheroid, and MEOS-API c6f423d2ef, main fails to compile: "method nad_stbox_stbox in class functions.GeneratedFunctions cannot be applied to given types; required: jnr.ffi.Pointer,jnr.ffi.Pointer,boolean", 25 calls in TPoint, STBox and Temporal. With the jar built, the Spark gaps step refuses adwithin_tgeoarr_tgeoarr, edwithin_tgeoarr_tgeoarr, mindistance_tgeoarr_tgeoarr and tdwithin_tgeoarr_tgeoarr, the facade emits no aDwithinPairs, eDwithinPairs or tDwithinPairs, and MobilitySpark's GeneratedSurfaceTest fails on "The `dynTimeWarpPath` requires 3 parameters but the actual number is 2". Measured over the same pair: mvn clean install passes 106 codegen and 1800 core tests, none skipped, with no warning; the Spark surface registers 13 NxN UDFs where main registers 10, the same 53 C-named and 12 SQL-named set-returning UDFs, and the gaps step reports none new; the facade emits the three folded forms with their spheroid; the typed flink-sql and spark-sql surfaces each register 1154 SQL functions and 8749 overloads, as with main's generator. With this jar and these generators MobilitySpark main passes its 35 tests, and MobilityFlink, its benchmark passing spheroid as fix/pass-spheroid does, its 21 binding and 12 benchmark tests. Why: a binding passes every argument of the MEOS function it calls, a SQL name answers the call SQL accepts, and a measure keeps the model of the earth its caller had. --- .../main/java/types/basic/tpoint/TPoint.java | 38 ++++++------ .../src/main/java/types/boxes/STBox.java | 6 +- .../main/java/types/temporal/Temporal.java | 6 +- .../GeneratedTemporalParityTest.java | 10 ++-- tools/codegen_jvm.py | 5 +- tools/codegen_spark_udfs.py | 59 +++++++++++++++---- 6 files changed, 79 insertions(+), 45 deletions(-) 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)