From f9fbbec3234544ca4223ef89fa563081da72654d Mon Sep 17 00:00:00 2001 From: gmantele <gmantele@ari.uni-heidelberg.de> Date: Thu, 16 Apr 2020 18:31:18 +0200 Subject: [PATCH] [ADQL] Allow also `CENTROID` and UDF as arguments of `DISTANCE` (2-arg form) & Allow UDF as valid geometry argument (a UDF being able to return geometries) like in functions `AREA`, `CENTROID`, `DISTANCE`, ... --- src/adql/parser/ADQLQueryFactory.java | 15 ++++-- src/adql/parser/grammar/adqlGrammar200.jj | 10 ++-- src/adql/parser/grammar/adqlGrammar201.jj | 54 +++++++++++++------ .../function/geometry/DistanceFunction.java | 49 ++++++++++------- .../function/geometry/GeometryFunction.java | 43 ++++++++++++--- test/adql/db/TestDBChecker.java | 14 ++--- 6 files changed, 125 insertions(+), 60 deletions(-) diff --git a/src/adql/parser/ADQLQueryFactory.java b/src/adql/parser/ADQLQueryFactory.java index 5507f7a..5829f32 100644 --- a/src/adql/parser/ADQLQueryFactory.java +++ b/src/adql/parser/ADQLQueryFactory.java @@ -16,7 +16,7 @@ package adql.parser; * You should have received a copy of the GNU Lesser General Public License * along with ADQLLibrary. If not, see <http://www.gnu.org/licenses/>. * - * Copyright 2012-2019 - UDS/Centre de Données astronomiques de Strasbourg (CDS), + * Copyright 2012-2020 - UDS/Centre de Données astronomiques de Strasbourg (CDS), * Astronomisches Rechen Institut (ARI) */ @@ -92,7 +92,7 @@ import adql.query.operand.function.string.LowerFunction; * </p> * * @author Grégory Mantelet (CDS;ARI) - * @version 2.0 (11/2019) + * @version 2.0 (04/2020) * * @see ADQLParser */ @@ -349,11 +349,18 @@ public class ADQLQueryFactory { return new DefaultUDF(name, params); } + /** @deprecated Since 2.0, prefer to use directly {@link #createDistance(GeometryFunction, GeometryFunction)} */ + @Deprecated public DistanceFunction createDistance(PointFunction point1, PointFunction point2) throws Exception { - return new DistanceFunction(new GeometryValue<PointFunction>(point1), new GeometryValue<PointFunction>(point2)); + return createDistance((GeometryFunction)point1, (GeometryFunction)point2); + } + + /** @since 2.0 */ + public DistanceFunction createDistance(GeometryFunction point1, GeometryFunction point2) throws Exception { + return new DistanceFunction(new GeometryValue<GeometryFunction>(point1), new GeometryValue<GeometryFunction>(point2)); } - public DistanceFunction createDistance(GeometryValue<PointFunction> point1, GeometryValue<PointFunction> point2) throws Exception { + public DistanceFunction createDistance(GeometryValue<GeometryFunction> point1, GeometryValue<GeometryFunction> point2) throws Exception { return new DistanceFunction(point1, point2); } diff --git a/src/adql/parser/grammar/adqlGrammar200.jj b/src/adql/parser/grammar/adqlGrammar200.jj index 360c11c..bdcf299 100644 --- a/src/adql/parser/grammar/adqlGrammar200.jj +++ b/src/adql/parser/grammar/adqlGrammar200.jj @@ -1183,7 +1183,7 @@ ADQLOperand[] Coordinates(): {ADQLOperand[] ops = new ADQLOperand[2];} { {return ops;} } -GeometryFunction GeometryFunction(): {Token fct=null, end; GeometryValue<GeometryFunction> gvf1, gvf2; GeometryValue<PointFunction> gvp1, gvp2; GeometryFunction gf = null; PointFunction p1=null, p2=null; ADQLColumn col1 = null, col2 = null;} { +GeometryFunction GeometryFunction(): {Token fct=null, end; GeometryValue<GeometryFunction> gvf1, gvf2; GeometryValue<GeometryFunction> gvp1, gvp2; GeometryFunction gf = null; PointFunction p1=null, p2=null; ADQLColumn col1 = null, col2 = null;} { try{ // predicate_geometry_function ( @@ -1203,20 +1203,20 @@ GeometryFunction GeometryFunction(): {Token fct=null, end; GeometryValue<Geometr (p1=Point()|col1=Column()) { if (p1 != null) - gvp1 = new GeometryValue<PointFunction>(p1); + gvp1 = new GeometryValue<GeometryFunction>(p1); else{ col1.setExpectedType('G'); - gvp1 = new GeometryValue<PointFunction>(col1); + gvp1 = new GeometryValue<GeometryFunction>(col1); } } <COMMA> (p2=Point()|col2=Column()) { if (p2 != null) - gvp2 = new GeometryValue<PointFunction>(p2); + gvp2 = new GeometryValue<GeometryFunction>(p2); else{ col2.setExpectedType('G'); - gvp2 = new GeometryValue<PointFunction>(col2); + gvp2 = new GeometryValue<GeometryFunction>(col2); } } end=<RIGHT_PAR> diff --git a/src/adql/parser/grammar/adqlGrammar201.jj b/src/adql/parser/grammar/adqlGrammar201.jj index 3242668..99f0718 100644 --- a/src/adql/parser/grammar/adqlGrammar201.jj +++ b/src/adql/parser/grammar/adqlGrammar201.jj @@ -31,7 +31,7 @@ * ParseException is thrown. * * Author: Grégory Mantelet (CDS) -* Version: 2.0 (03/2020) +* Version: 2.0 (04/2020) */ /* ########### */ @@ -107,7 +107,7 @@ import adql.query.operand.function.geometry.GeometryFunction.GeometryValue; * @see ADQLParser * * @author Grégory Mantelet (CDS) - * @version 2.0 (03/2020) + * @version 2.0 (04/2020) * @since 2.0 */ public class ADQLGrammar201 extends ADQLGrammarBase { @@ -1030,12 +1030,15 @@ ADQLOperand StringFactor(): {ADQLOperand op;} { {return op;} } -GeometryValue<GeometryFunction> GeometryExpression(): {ADQLColumn col = null; GeometryFunction gf = null;} { - (col=Column() | gf=GeometryValueFunction()) +GeometryValue<GeometryFunction> GeometryExpression(): {ADQLColumn col = null; UserDefinedFunction udf = null; GeometryFunction gf = null;} { + ( gf=GeometryValueFunction() | LOOKAHEAD(2) udf=UserDefinedFunction() | col=Column()) { if (col != null){ col.setExpectedType('G'); return new GeometryValue<GeometryFunction>(col); + }else if (udf != null){ + udf.setExpectedType('G'); + return new GeometryValue<GeometryFunction>(udf); }else return new GeometryValue<GeometryFunction>(gf); } @@ -1277,7 +1280,7 @@ GeometryFunction GeometryFunction(): {Token fct=null, end=null; GeometryValue<Ge } } -DistanceFunction DistanceFunction(): { Token fct=null, end=null; DistanceFunction gf; ADQLOperand lon, lat; GeometryValue<PointFunction> gvp1, gvp2; } { +DistanceFunction DistanceFunction(): { Token fct=null, end=null; DistanceFunction gf; ADQLOperand lon, lat; GeometryValue<GeometryFunction> gvp1, gvp2; } { try { // DISTANCE(POINT,POINT) (LOOKAHEAD(DistanceFunction2()) @@ -1286,10 +1289,10 @@ DistanceFunction DistanceFunction(): { Token fct=null, end=null; DistanceFunctio // DISTANCE(lon1, lat1, lon2, lat2) fct=<DISTANCE> <LEFT_PAR> lon=NumericExpression() <COMMA> lat=NumericExpression() - { gvp1 = new GeometryValue<PointFunction>(queryFactory.createPoint(null, lon, lat)); } + { gvp1 = new GeometryValue<GeometryFunction>(queryFactory.createPoint(null, lon, lat)); } <COMMA> lon=NumericExpression() <COMMA> lat=NumericExpression() - { gvp2 = new GeometryValue<PointFunction>(queryFactory.createPoint(null, lon, lat)); } + { gvp2 = new GeometryValue<GeometryFunction>(queryFactory.createPoint(null, lon, lat)); } end=<RIGHT_PAR> { gf = queryFactory.createDistance(gvp1, gvp2); @@ -1302,26 +1305,32 @@ DistanceFunction DistanceFunction(): { Token fct=null, end=null; DistanceFunctio } } -DistanceFunction DistanceFunction2(): { Token fct=null, end=null; DistanceFunction gf; GeometryValue<PointFunction> gvp1, gvp2; PointFunction p1=null, p2=null; ADQLColumn col1=null, col2=null; } { +DistanceFunction DistanceFunction2(): { Token fct=null, end=null; DistanceFunction gf; GeometryValue<GeometryFunction> gvp1, gvp2; GeometryFunction p1=null, p2=null; ADQLColumn col1=null, col2=null; UserDefinedFunction udf1=null, udf2=null; } { try { fct=<DISTANCE> <LEFT_PAR> - (p1=Point()|col1=Column()) + (p1=Point()|p1=Centroid()|LOOKAHEAD(2) udf1=UserDefinedFunction()|col1=Column()) { if (p1 != null) - gvp1 = new GeometryValue<PointFunction>(p1); - else{ + gvp1 = new GeometryValue<GeometryFunction>(p1); + else if (udf1 != null){ + udf1.setExpectedType('G'); + gvp1 = new GeometryValue<GeometryFunction>(udf1); + }else{ col1.setExpectedType('G'); - gvp1 = new GeometryValue<PointFunction>(col1); + gvp1 = new GeometryValue<GeometryFunction>(col1); } } <COMMA> - (p2=Point()|col2=Column()) + (p2=Point()|p2=Centroid()|LOOKAHEAD(2) udf2=UserDefinedFunction()|col2=Column()) { if (p2 != null) - gvp2 = new GeometryValue<PointFunction>(p2); - else{ + gvp2 = new GeometryValue<GeometryFunction>(p2); + else if (udf2 != null){ + udf2.setExpectedType('G'); + gvp2 = new GeometryValue<GeometryFunction>(udf2); + }else{ col2.setExpectedType('G'); - gvp2 = new GeometryValue<PointFunction>(col2); + gvp2 = new GeometryValue<GeometryFunction>(col2); } } end=<RIGHT_PAR> @@ -1353,7 +1362,7 @@ GeometryFunction GeometryValueFunction(): {Token fct=null, end=null; ADQLOperand {gf = queryFactory.createBox(null, coords[0], coords[1], width, height);} // CENTROID: - | (fct=<CENTROID> <LEFT_PAR> gvf=GeometryExpression() end=<RIGHT_PAR>) {gf = queryFactory.createCentroid(gvf);} + | gf=Centroid() // CIRCLE (deprecated since ADQL-2.1) | LOOKAHEAD(CircleWithCooSys()) gf=CircleWithCooSys() @@ -1424,6 +1433,17 @@ GeometryFunction CircleWithCooSys(): { Token fct=null, end=null; ADQLOperand coo } } +CentroidFunction Centroid(): {Token fct=null, end=null; GeometryValue<GeometryFunction> gvf = null; } { + (fct=<CENTROID> <LEFT_PAR> gvf=GeometryExpression() end=<RIGHT_PAR>) + { + try { + return queryFactory.createCentroid(gvf); + }catch(Exception ex){ + throw generateParseException(ex); + } + } +} + PointFunction Point(): {Token start, end; ADQLOperand[] coords; PointFunction pf;} { // POINT (depecrated since ADQL-2.1) (LOOKAHEAD(PointWithCooSys()) diff --git a/src/adql/query/operand/function/geometry/DistanceFunction.java b/src/adql/query/operand/function/geometry/DistanceFunction.java index e57c275..1065340 100644 --- a/src/adql/query/operand/function/geometry/DistanceFunction.java +++ b/src/adql/query/operand/function/geometry/DistanceFunction.java @@ -16,7 +16,7 @@ package adql.query.operand.function.geometry; * You should have received a copy of the GNU Lesser General Public License * along with ADQLLibrary. If not, see <http://www.gnu.org/licenses/>. * - * Copyright 2012-2019 - UDS/Centre de Données astronomiques de Strasbourg (CDS), + * Copyright 2012-2020 - UDS/Centre de Données astronomiques de Strasbourg (CDS), * Astronomisches Rechen Institut (ARI) */ @@ -39,6 +39,15 @@ import adql.query.operand.ADQLOperand; * accepts four separate numeric values. * </p> * + * <p><i><b>Implementation note:</b> + * In this current implementation, the 2-argument form allows 2 geometries + * instead of 2 points. The goal is to be more generic. POINT is supposed to + * be the main expected type of argument, but it could also be a CENTROID + * (which returns a POINT). Moreover, some extension of this library might + * want to support DISTANCE between any type of geometries instead of just + * points. + * </i></p> + * * <p> * If an ADQL service implementation declares support for DISTANCE, then it * must implement both the two parameter and four parameter forms of the @@ -67,8 +76,8 @@ import adql.query.operand.ADQLOperand; * <i> * <p><b>Example:</b></p> * <p> - * The distance between to points stored in the database could be calculated as - * follows: + * The distance between two points stored in the database could be calculated + * as follows: * </p> * <pre>DISTANCE(t1.base, t2.target)</pre> * <p> @@ -92,7 +101,7 @@ import adql.query.operand.ADQLOperand; * </p> * * @author Grégory Mantelet (CDS;ARI) - * @version 2.0 (07/2019) + * @version 2.0 (04/2020) */ public class DistanceFunction extends GeometryFunction { @@ -101,10 +110,10 @@ public class DistanceFunction extends GeometryFunction { public static final LanguageFeature FEATURE = new LanguageFeature(LanguageFeature.TYPE_ADQL_GEO, "DISTANCE", true, "Compute the arc length along a great circle between two points and returns a numeric value expression in degrees."); /** The first point. */ - private GeometryValue<PointFunction> p1; + private GeometryValue<GeometryFunction> p1; /** The second point. */ - private GeometryValue<PointFunction> p2; + private GeometryValue<GeometryFunction> p2; /** * Builds a DISTANCE function. @@ -113,7 +122,7 @@ public class DistanceFunction extends GeometryFunction { * @param point2 The second point. * @throws NullPointerException If one of the parameters are incorrect. */ - public DistanceFunction(GeometryValue<PointFunction> point1, GeometryValue<PointFunction> point2) throws NullPointerException { + public DistanceFunction(GeometryValue<GeometryFunction> point1, GeometryValue<GeometryFunction> point2) throws NullPointerException { super(); if (point1 == null || point2 == null) throw new NullPointerException("All parameters of the DISTANCE function must be different from null!"); @@ -131,8 +140,8 @@ public class DistanceFunction extends GeometryFunction { @SuppressWarnings("unchecked") public DistanceFunction(DistanceFunction toCopy) throws Exception { super(toCopy); - p1 = (GeometryValue<PointFunction>)(toCopy.p1.getCopy()); - p2 = (GeometryValue<PointFunction>)(toCopy.p2.getCopy()); + p1 = (GeometryValue<GeometryFunction>)(toCopy.p1.getCopy()); + p2 = (GeometryValue<GeometryFunction>)(toCopy.p2.getCopy()); } @Override @@ -175,7 +184,7 @@ public class DistanceFunction extends GeometryFunction { * * @return A point. */ - public final GeometryValue<PointFunction> getP1() { + public final GeometryValue<GeometryFunction> getP1() { return p1; } @@ -184,7 +193,7 @@ public class DistanceFunction extends GeometryFunction { * * @param p1 A point. */ - public final void setP1(GeometryValue<PointFunction> p1) { + public final void setP1(GeometryValue<GeometryFunction> p1) { this.p1 = p1; setPosition(null); } @@ -194,7 +203,7 @@ public class DistanceFunction extends GeometryFunction { * * @return A point. */ - public final GeometryValue<PointFunction> getP2() { + public final GeometryValue<GeometryFunction> getP2() { return p2; } @@ -203,7 +212,7 @@ public class DistanceFunction extends GeometryFunction { * * @param p2 A point. */ - public final void setP2(GeometryValue<PointFunction> p2) { + public final void setP2(GeometryValue<GeometryFunction> p2) { this.p2 = p2; setPosition(null); } @@ -235,23 +244,23 @@ public class DistanceFunction extends GeometryFunction { public ADQLOperand setParameter(int index, ADQLOperand replacer) throws ArrayIndexOutOfBoundsException, NullPointerException, Exception { if (replacer == null) throw new NullPointerException("Impossible to remove a parameter from the function " + getName() + "!"); - else if (!(replacer instanceof GeometryValue || replacer instanceof ADQLColumn || replacer instanceof PointFunction)) - throw new Exception("Impossible to replace a GeometryValue/Column/PointFunction by " + replacer.getClass().getName() + " (" + replacer.toADQL() + ")!"); + else if (!(replacer instanceof GeometryValue || replacer instanceof ADQLColumn || replacer instanceof GeometryFunction)) + throw new Exception("Impossible to replace a GeometryValue/Column/GeometryFunction by " + replacer.getClass().getName() + " (" + replacer.toADQL() + ")!"); ADQLOperand replaced = null; - GeometryValue<PointFunction> toUpdate = null; + GeometryValue<GeometryFunction> toUpdate = null; switch(index) { case 0: replaced = p1.getValue(); if (replacer instanceof GeometryValue) - p1 = (GeometryValue<PointFunction>)replacer; + p1 = (GeometryValue<GeometryFunction>)replacer; else toUpdate = p1; break; case 1: replaced = p2.getValue(); if (replacer instanceof GeometryValue) - p2 = (GeometryValue<PointFunction>)replacer; + p2 = (GeometryValue<GeometryFunction>)replacer; else toUpdate = p2; break; @@ -262,8 +271,8 @@ public class DistanceFunction extends GeometryFunction { if (toUpdate != null) { if (replacer instanceof ADQLColumn) toUpdate.setColumn((ADQLColumn)replacer); - else if (replacer instanceof PointFunction) - toUpdate.setGeometry((PointFunction)replacer); + else if (replacer instanceof GeometryFunction) + toUpdate.setGeometry((GeometryFunction)replacer); } setPosition(null); diff --git a/src/adql/query/operand/function/geometry/GeometryFunction.java b/src/adql/query/operand/function/geometry/GeometryFunction.java index 6669a04..629cf85 100644 --- a/src/adql/query/operand/function/geometry/GeometryFunction.java +++ b/src/adql/query/operand/function/geometry/GeometryFunction.java @@ -16,7 +16,7 @@ package adql.query.operand.function.geometry; * You should have received a copy of the GNU Lesser General Public License * along with ADQLLibrary. If not, see <http://www.gnu.org/licenses/>. * - * Copyright 2012-2019 - UDS/Centre de Données astronomiques de Strasbourg (CDS), + * Copyright 2012-2020 - UDS/Centre de Données astronomiques de Strasbourg (CDS), * Astronomisches Rechen Institut (ARI) */ @@ -29,6 +29,7 @@ import adql.query.operand.ADQLColumn; import adql.query.operand.ADQLOperand; import adql.query.operand.StringConstant; import adql.query.operand.function.ADQLFunction; +import adql.query.operand.function.UserDefinedFunction; /** * It represents any geometric function of ADQL. @@ -109,14 +110,20 @@ public abstract class GeometryFunction extends ADQLFunction { /** * This class represents a parameter of a geometry function - * which, in general, is either a GeometryFunction or a Column. + * which, in general, is either a GeometryFunction, a Column or a + * UserDefinedFunction. * * @author Grégory Mantelet (CDS;ARI) - * @version 2.0 (07/2019) + * @version 2.0 (04/2020) */ public static final class GeometryValue<F extends GeometryFunction> implements ADQLOperand { + private ADQLColumn column; private F geomFunct; + + /** @since 2.0 */ + private UserDefinedFunction udf; + /** Position of this {@link GeometryValue} in the ADQL query string. * @since 1.4 */ private TextPosition position = null; @@ -125,16 +132,19 @@ public abstract class GeometryFunction extends ADQLFunction { if (col == null) throw new NullPointerException("Impossible to build a GeometryValue without a column or a geometry function!"); setColumn(col); - if (col.getPosition() != null) - position = col.getPosition(); } public GeometryValue(F geometry) throws NullPointerException { if (geometry == null) throw new NullPointerException("Impossible to build a GeometryValue without a column or a geometry function!"); setGeometry(geometry); - if (geometry.getPosition() != null) - position = geometry.getPosition(); + } + + /** @since 2.0 */ + public GeometryValue(UserDefinedFunction udf) throws NullPointerException { + if (udf == null) + throw new NullPointerException("Impossible to build a GeometryValue without a column, a geometry function or User Defined Function!"); + setUDF(udf); } @SuppressWarnings("unchecked") @@ -151,6 +161,7 @@ public abstract class GeometryFunction extends ADQLFunction { public void setColumn(ADQLColumn col) { if (col != null) { + udf = null; geomFunct = null; column = col; position = (column.getPosition() != null) ? column.getPosition() : null; @@ -159,14 +170,30 @@ public abstract class GeometryFunction extends ADQLFunction { public void setGeometry(F geometry) { if (geometry != null) { + udf = null; column = null; geomFunct = geometry; position = (geomFunct.getPosition() != null) ? geomFunct.getPosition() : null; } } + /** @since 2.0 */ + public void setUDF(UserDefinedFunction udf) { + if (udf != null) { + column = null; + geomFunct = null; + this.udf = udf; + position = (udf.getPosition() != null) ? udf.getPosition() : null; + } + } + public ADQLOperand getValue() { - return (column != null) ? column : geomFunct; + if (column != null) + return column; + else if (geomFunct != null) + return geomFunct; + else + return udf; } public boolean isColumn() { diff --git a/test/adql/db/TestDBChecker.java b/test/adql/db/TestDBChecker.java index 012decc..b150a2b 100644 --- a/test/adql/db/TestDBChecker.java +++ b/test/adql/db/TestDBChecker.java @@ -653,12 +653,14 @@ public class TestDBChecker { } // Test the return type checking inside a whole query: - try { - parser.parseQuery("SELECT CONTAINS(colG, titi()) ' AS \"Super\" FROM foo;"); - fail("Geometrical UDFs are not allowed for the moment in the ADQL language: this test should have failed!"); - } catch(ParseException e1) { - assertTrue(e1 instanceof ParseException); - assertEquals(" Encountered \"(\". Was expecting one of: \")\" \".\" \".\" \")\" ", e1.getMessage()); + if (parser.getADQLVersion() == ADQLVersion.V2_0) { + try { + parser.parseQuery("SELECT CONTAINS(colG, titi()) AS \"SuperError\" FROM foo;"); + fail("Geometrical UDFs are not allowed for the moment in the ADQL language: this test should have failed!"); + } catch(ParseException e1) { + assertTrue(e1 instanceof ParseException); + assertEquals(" Encountered \"(\". Was expecting one of: \")\" \".\" \".\" \")\" ", e1.getMessage()); + } } try { parser.parseQuery("SELECT titi()*3 AS \"SuperError\" FROM foo;"); -- GitLab