From 046ac6911b9a3a5d420cb99ebdb0c6ed284d6a8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9gory=20Mantelet?= <gregory.mantelet@astro.unistra.fr> Date: Wed, 21 Aug 2019 10:28:10 +0200 Subject: [PATCH] [ADQL] Efficient PgSphere translation of the Preferred X-Match Syntax described in the ADQL-2.1 standard (in section 4.2.7). Currently, geometries are not translated in MySQL and MS-SQL Server translators. So, the preferred xmatch syntax has not been implemented in these translators. --- src/adql/translator/PgSphereTranslator.java | 190 ++++++++++++------ .../translator/TestPgSphereTranslator.java | 106 ++++++---- 2 files changed, 205 insertions(+), 91 deletions(-) diff --git a/src/adql/translator/PgSphereTranslator.java b/src/adql/translator/PgSphereTranslator.java index 7c34e1a..ef8cfdb 100644 --- a/src/adql/translator/PgSphereTranslator.java +++ b/src/adql/translator/PgSphereTranslator.java @@ -16,7 +16,7 @@ package adql.translator; * 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-2017 - UDS/Centre de DonnĂ©es astronomiques de Strasbourg (CDS), + * Copyright 2012-2019 - UDS/Centre de DonnĂ©es astronomiques de Strasbourg (CDS), * Astronomisches Rechen Institut (ARI) */ @@ -32,6 +32,8 @@ import adql.parser.grammar.ParseException; import adql.query.TextPosition; import adql.query.constraint.Comparison; import adql.query.constraint.ComparisonOperator; +import adql.query.operand.ADQLOperand; +import adql.query.operand.StringConstant; import adql.query.operand.function.geometry.AreaFunction; import adql.query.operand.function.geometry.BoxFunction; import adql.query.operand.function.geometry.CentroidFunction; @@ -51,8 +53,15 @@ import adql.query.operand.function.geometry.PolygonFunction; * class. The other functions are managed by {@link PostgreSQLTranslator}. * </p> * + * <p><i><b>Implementation note:</b> + * The preferred xmatch syntax described in the section 4.2.7 of the ADQL + * standard (here ADQL-2.1) is implemented here so that such query is as + * efficient as a <code>CONTAINS(POINT(...), CIRCLE(...)) = 1</code>. + * See {@link #translate(adql.query.constraint.Comparison)} for more details. + * </i></p> + * * @author Grégory Mantelet (CDS;ARI) - * @version 1.4 (07/2017) + * @version 2.0 (08/2019) */ public class PgSphereTranslator extends PostgreSQLTranslator { @@ -68,7 +77,7 @@ public class PgSphereTranslator extends PostgreSQLTranslator { * * @see PostgreSQLTranslator#PostgreSQLTranslator() */ - public PgSphereTranslator(){ + public PgSphereTranslator() { super(); } @@ -80,7 +89,7 @@ public class PgSphereTranslator extends PostgreSQLTranslator { * * @see PostgreSQLTranslator#PostgreSQLTranslator(boolean) */ - public PgSphereTranslator(boolean allCaseSensitive){ + public PgSphereTranslator(boolean allCaseSensitive) { super(allCaseSensitive); } @@ -94,12 +103,12 @@ public class PgSphereTranslator extends PostgreSQLTranslator { * * @see PostgreSQLTranslator#PostgreSQLTranslator(boolean, boolean, boolean, boolean) */ - public PgSphereTranslator(boolean catalog, boolean schema, boolean table, boolean column){ + public PgSphereTranslator(boolean catalog, boolean schema, boolean table, boolean column) { super(catalog, schema, table, column); } @Override - public String translate(PointFunction point) throws TranslationException{ + public String translate(PointFunction point) throws TranslationException { StringBuffer str = new StringBuffer("spoint("); str.append("radians(").append(translate(point.getCoord1())).append("),"); str.append("radians(").append(translate(point.getCoord2())).append("))"); @@ -107,7 +116,7 @@ public class PgSphereTranslator extends PostgreSQLTranslator { } @Override - public String translate(CircleFunction circle) throws TranslationException{ + public String translate(CircleFunction circle) throws TranslationException { StringBuffer str = new StringBuffer("scircle("); str.append("spoint(radians(").append(translate(circle.getCoord1())).append("),"); str.append("radians(").append(translate(circle.getCoord2())).append(")),"); @@ -116,7 +125,7 @@ public class PgSphereTranslator extends PostgreSQLTranslator { } @Override - public String translate(BoxFunction box) throws TranslationException{ + public String translate(BoxFunction box) throws TranslationException { StringBuffer str = new StringBuffer("sbox("); str.append("spoint(").append("radians(").append(translate(box.getCoord1())).append("-(").append(translate(box.getWidth())).append("/2.0)),"); @@ -129,15 +138,15 @@ public class PgSphereTranslator extends PostgreSQLTranslator { } @Override - public String translate(PolygonFunction polygon) throws TranslationException{ - try{ + public String translate(PolygonFunction polygon) throws TranslationException { + try { StringBuffer str = new StringBuffer("spoly('{'"); - if (polygon.getNbParameters() > 2){ + if (polygon.getNbParameters() > 2) { PointFunction point = new PointFunction(polygon.getCoordinateSystem(), polygon.getParameter(1), polygon.getParameter(2)); str.append(" || ").append(translate(point)); - for(int i = 3; i < polygon.getNbParameters() && i + 1 < polygon.getNbParameters(); i += 2){ + for(int i = 3; i < polygon.getNbParameters() && i + 1 < polygon.getNbParameters(); i += 2) { point.setCoord1(polygon.getParameter(i)); point.setCoord2(polygon.getParameter(i + 1)); str.append(" || ',' || ").append(translate(point)); @@ -147,14 +156,14 @@ public class PgSphereTranslator extends PostgreSQLTranslator { str.append(" || '}')"); return str.toString(); - }catch(Exception e){ + } catch(Exception e) { e.printStackTrace(); throw new TranslationException(e); } } @Override - public String translate(ExtractCoord extractCoord) throws TranslationException{ + public String translate(ExtractCoord extractCoord) throws TranslationException { StringBuffer str = new StringBuffer("degrees("); if (extractCoord.getName().equalsIgnoreCase("COORD1")) str.append("long("); @@ -165,52 +174,118 @@ public class PgSphereTranslator extends PostgreSQLTranslator { } @Override - public String translate(DistanceFunction fct) throws TranslationException{ + public String translate(DistanceFunction fct) throws TranslationException { StringBuffer str = new StringBuffer("degrees("); str.append(translate(fct.getP1())).append(" <-> ").append(translate(fct.getP2())).append(")"); return str.toString(); } @Override - public String translate(AreaFunction areaFunction) throws TranslationException{ + public String translate(AreaFunction areaFunction) throws TranslationException { StringBuffer str = new StringBuffer("degrees(degrees(area("); str.append(translate(areaFunction.getParameter())).append(")))"); return str.toString(); } @Override - public String translate(CentroidFunction centroidFunction) throws TranslationException{ + public String translate(CentroidFunction centroidFunction) throws TranslationException { StringBuffer str = new StringBuffer("center("); str.append(translate(centroidFunction.getParameter(0))).append(")"); return str.toString(); } @Override - public String translate(ContainsFunction fct) throws TranslationException{ + public String translate(ContainsFunction fct) throws TranslationException { StringBuffer str = new StringBuffer("("); str.append(translate(fct.getLeftParam())).append(" @ ").append(translate(fct.getRightParam())).append(")"); return str.toString(); } @Override - public String translate(IntersectsFunction fct) throws TranslationException{ + public String translate(IntersectsFunction fct) throws TranslationException { StringBuffer str = new StringBuffer("("); str.append(translate(fct.getLeftParam())).append(" && ").append(translate(fct.getRightParam())).append(")"); return str.toString(); } @Override - public String translate(Comparison comp) throws TranslationException{ + public String translate(Comparison comp) throws TranslationException { + + // CONTAINS or INTERSECTS(...) on left: if ((comp.getLeftOperand() instanceof ContainsFunction || comp.getLeftOperand() instanceof IntersectsFunction) && (comp.getOperator() == ComparisonOperator.EQUAL || comp.getOperator() == ComparisonOperator.NOT_EQUAL) && comp.getRightOperand().isNumeric()) return translate(comp.getLeftOperand()) + " " + comp.getOperator().toADQL() + " '" + translate(comp.getRightOperand()) + "'"; + + // CONTAINS or INTERSECTS(...) on right: else if ((comp.getRightOperand() instanceof ContainsFunction || comp.getRightOperand() instanceof IntersectsFunction) && (comp.getOperator() == ComparisonOperator.EQUAL || comp.getOperator() == ComparisonOperator.NOT_EQUAL) && comp.getLeftOperand().isNumeric()) return "'" + translate(comp.getLeftOperand()) + "' " + comp.getOperator().toADQL() + " " + translate(comp.getRightOperand()); + + // Preferred xmatch syntax described in the ADQL standard: + else if (isPreferredXmatchSyntax(comp)) { + // extract the DISTANCE and its compared value: + DistanceFunction distFct; + ADQLOperand numericOperand; + if (comp.getLeftOperand() instanceof DistanceFunction) { + distFct = (DistanceFunction)comp.getLeftOperand(); + numericOperand = comp.getRightOperand(); + } else { + distFct = (DistanceFunction)comp.getRightOperand(); + numericOperand = comp.getLeftOperand(); + } + try { + // build the CIRCLE to use in the artificial CONTAINS: + CircleFunction circleFct = new CircleFunction(new StringConstant(""), ((PointFunction)distFct.getParameter(1)).getCoord1(), ((PointFunction)distFct.getParameter(1)).getCoord2(), numericOperand); + // adapt the translation in function of the comp. operator: + switch(comp.getOperator()) { + case LESS_THAN: + return "((" + translate(distFct.getParameter(0)) + " @ " + translate(circleFct) + ") = '1' AND " + super.translate(comp) + ")"; + case LESS_OR_EQUAL: + return "((" + translate(distFct.getParameter(0)) + " @ " + translate(circleFct) + ") = '1'" + ")"; + case GREATER_THAN: + return "((" + translate(distFct.getParameter(0)) + " @ " + translate(circleFct) + ") = '0' AND " + super.translate(comp) + ")"; + case GREATER_OR_EQUAL: + return "((" + translate(distFct.getParameter(0)) + " @ " + translate(circleFct) + ") = '0'" + ")"; + default: // theoretically, this case never happens! + return super.translate(comp); + } + } catch(Exception ex) { + throw new TranslationException("Impossible to translate the following xmatch syntax: \"" + comp.toADQL() + "\"! Cause: " + ex.getMessage(), ex); + } + } + // Any other comparison: else return super.translate(comp); } + /** + * Test whether the given comparison corresponds to the preferred xmatch + * syntax described in the ADQL standard. + * + * <p> + * In other words, this function returns <code>true</code> if the following + * conditions are met: + * </p> + * <ul> + * <li>the left operand is DISTANCE and the right one is a numeric, + * or vice-versa,</li> + * <li>and the comparison operator is <, ≤, > or ≥.</li> + * </ul> + * + * @param comp The comparison to test. + * + * @return <code>true</code> if it corresponds to a valid xmatch syntax, + * <code>false</code> otherwise. + * + * @since 2.0 + */ + protected boolean isPreferredXmatchSyntax(final Comparison comp) { + if ((comp.getLeftOperand() instanceof DistanceFunction && comp.getRightOperand().isNumeric()) || (comp.getLeftOperand().isNumeric() && comp.getRightOperand() instanceof DistanceFunction)) + return (comp.getOperator() == ComparisonOperator.LESS_THAN || comp.getOperator() == ComparisonOperator.LESS_OR_EQUAL || comp.getOperator() == ComparisonOperator.GREATER_THAN || comp.getOperator() == ComparisonOperator.GREATER_OR_EQUAL); + else + return false; + } + @Override - public DBType convertTypeFromDB(final int dbmsType, final String rawDbmsTypeName, String dbmsTypeName, final String[] params){ + public DBType convertTypeFromDB(final int dbmsType, final String rawDbmsTypeName, String dbmsTypeName, final String[] params) { // If no type is provided return VARCHAR: if (dbmsTypeName == null || dbmsTypeName.trim().length() == 0) return null; @@ -227,8 +302,8 @@ public class PgSphereTranslator extends PostgreSQLTranslator { } @Override - public String convertTypeToDB(final DBType type){ - if (type != null){ + public String convertTypeToDB(final DBType type) { + if (type != null) { if (type.type == DBDatatype.POINT) return "spoint"; else if (type.type == DBDatatype.REGION) @@ -238,7 +313,7 @@ public class PgSphereTranslator extends PostgreSQLTranslator { } @Override - public Region translateGeometryFromDB(final Object jdbcColValue) throws ParseException{ + public Region translateGeometryFromDB(final Object jdbcColValue) throws ParseException { // A NULL value stays NULL: if (jdbcColValue == null) return null; @@ -271,17 +346,17 @@ public class PgSphereTranslator extends PostgreSQLTranslator { } @Override - public Object translateGeometryToDB(final Region region) throws ParseException{ + public Object translateGeometryToDB(final Region region) throws ParseException { // A NULL value stays NULL: if (region == null) return null; - try{ + try { PGobject dbRegion = new PGobject(); StringBuffer buf; // Build the PgSphere expression from the given geometry in function of its type: - switch(region.type){ + switch(region.type) { case POSITION: dbRegion.setType("spoint"); @@ -291,7 +366,7 @@ public class PgSphereTranslator extends PostgreSQLTranslator { case POLYGON: dbRegion.setType("spoly"); buf = new StringBuffer("{"); - for(int i = 0; i < region.coordinates.length; i++){ + for(int i = 0; i < region.coordinates.length; i++) { if (i > 0) buf.append(','); buf.append('(').append(region.coordinates[i][0]).append("d,").append(region.coordinates[i][1]).append("d)"); @@ -324,7 +399,7 @@ public class PgSphereTranslator extends PostgreSQLTranslator { throw new ParseException("Unsupported geometrical region: \"" + region.type + "\"!"); } return dbRegion; - }catch(SQLException e){ + } catch(SQLException e) { /* This error could never happen! */ return null; } @@ -345,10 +420,10 @@ public class PgSphereTranslator extends PostgreSQLTranslator { * * @since 1.3 */ - protected String circleToPolygon(final double[] center, final double radius){ + protected String circleToPolygon(final double[] center, final double radius) { double angle = 0, x, y; StringBuffer buf = new StringBuffer(); - while(angle < 2 * Math.PI){ + while(angle < 2 * Math.PI) { x = center[0] + radius * Math.cos(angle); y = center[1] + radius * Math.sin(angle); if (buf.length() > 0) @@ -419,7 +494,7 @@ public class PgSphereTranslator extends PostgreSQLTranslator { private static final long serialVersionUID = 1L; /** Build a simple EOEException. */ - public EOEException(){ + public EOEException() { super("Unexpected End Of PgSphere Expression!"); } } @@ -427,14 +502,15 @@ public class PgSphereTranslator extends PostgreSQLTranslator { /** * Build the PgSphere parser. */ - public PgSphereGeometryParser(){} + public PgSphereGeometryParser() { + } /** * Prepare the parser in order to read the given PgSphere expression. * * @param newStcs New PgSphere expression to parse from now. */ - private void init(final String newExpr){ + private void init(final String newExpr) { expr = (newExpr == null) ? "" : newExpr; token = null; buffer = new StringBuffer(); @@ -447,7 +523,7 @@ public class PgSphereTranslator extends PostgreSQLTranslator { * * @throws ParseException If other non-space characters remains. */ - private void end() throws ParseException{ + private void end() throws ParseException { // Skip all spaces: skipSpaces(); @@ -464,7 +540,7 @@ public class PgSphereTranslator extends PostgreSQLTranslator { /** * Tool function which skips all next space characters until the next meaningful characters. */ - private void skipSpaces(){ + private void skipSpaces() { while(pos < expr.length() && Character.isWhitespace(expr.charAt(pos))) pos++; } @@ -480,7 +556,7 @@ public class PgSphereTranslator extends PostgreSQLTranslator { * * @return The full read word/token, or NULL if the end has been reached. */ - private String nextToken() throws EOEException{ + private String nextToken() throws EOEException { // Skip all spaces: skipSpaces(); @@ -489,8 +565,8 @@ public class PgSphereTranslator extends PostgreSQLTranslator { // Fetch all characters until word separator (a space or a open/close parenthesis): buffer.append(expr.charAt(pos++)); - if (!isSyntaxSeparator(buffer.charAt(0))){ - while(pos < expr.length() && !isSyntaxSeparator(expr.charAt(pos))){ + if (!isSyntaxSeparator(buffer.charAt(0))) { + while(pos < expr.length() && !isSyntaxSeparator(expr.charAt(pos))) { // skip eventual white-spaces: if (!Character.isWhitespace(expr.charAt(pos))) buffer.append(expr.charAt(pos)); @@ -515,7 +591,7 @@ public class PgSphereTranslator extends PostgreSQLTranslator { * * @return <i>true</i> if the given character must be considered as a separator, <i>false</i> otherwise. */ - private static boolean isSyntaxSeparator(final char c){ + private static boolean isSyntaxSeparator(final char c) { return (c == COMMA || c == DEGREE || c == HOUR || c == MINUTE || c == SECOND || c == OPEN_PAR || c == CLOSE_PAR || c == LESS_THAN || c == GREATER_THAN || c == OPEN_BRACE || c == CLOSE_BRACE); } @@ -527,7 +603,7 @@ public class PgSphereTranslator extends PostgreSQLTranslator { * * @throws ParseException If the next character is not matching the given one. */ - private void nextToken(final char expected) throws ParseException{ + private void nextToken(final char expected) throws ParseException { // Skip all spaces: skipSpaces(); @@ -537,7 +613,7 @@ public class PgSphereTranslator extends PostgreSQLTranslator { // Fetch the next character: char t = expr.charAt(pos++); - token = new String(new char[]{t}); + token = new String(new char[]{ t }); /* Test the the fetched character with the expected one * and throw an error if they don't match: */ @@ -554,7 +630,7 @@ public class PgSphereTranslator extends PostgreSQLTranslator { * * @throws ParseException If the PgSphere syntax of the given expression is wrong or does not correspond to a point. */ - public Region parsePoint(final String pgsphereExpr) throws ParseException{ + public Region parsePoint(final String pgsphereExpr) throws ParseException { // Init the parser: init(pgsphereExpr); // Parse the expression: @@ -575,13 +651,13 @@ public class PgSphereTranslator extends PostgreSQLTranslator { * @see #parseAngle() * @see #parsePoint(String) */ - private double[] parsePoint() throws ParseException{ + private double[] parsePoint() throws ParseException { nextToken(OPEN_PAR); double x = parseAngle(); nextToken(COMMA); double y = parseAngle(); nextToken(CLOSE_PAR); - return new double[]{x,y}; + return new double[]{ x, y }; } /** @@ -593,7 +669,7 @@ public class PgSphereTranslator extends PostgreSQLTranslator { * * @throws ParseException If the PgSphere syntax of the given expression is wrong or does not correspond to a circle. */ - public Region parseCircle(final String pgsphereExpr) throws ParseException{ + public Region parseCircle(final String pgsphereExpr) throws ParseException { // Init the parser: init(pgsphereExpr); @@ -620,7 +696,7 @@ public class PgSphereTranslator extends PostgreSQLTranslator { * * @throws ParseException If the PgSphere syntax of the given expression is wrong or does not correspond to a box. */ - public Region parseBox(final String pgsphereExpr) throws ParseException{ + public Region parseBox(final String pgsphereExpr) throws ParseException { // Init the parser: init(pgsphereExpr); @@ -637,7 +713,7 @@ public class PgSphereTranslator extends PostgreSQLTranslator { // Build the STC Box region: double width = Math.abs(northeast[0] - southwest[0]), height = Math.abs(northeast[1] - southwest[1]); - double[] center = new double[]{northeast[0] - width / 2,northeast[1] - height / 2}; + double[] center = new double[]{ northeast[0] - width / 2, northeast[1] - height / 2 }; return new Region(null, center, width, height); } @@ -650,7 +726,7 @@ public class PgSphereTranslator extends PostgreSQLTranslator { * * @throws ParseException If the PgSphere syntax of the given expression is wrong or does not correspond to a point. */ - public Region parsePolygon(final String pgsphereExpr) throws ParseException{ + public Region parsePolygon(final String pgsphereExpr) throws ParseException { // Init the parser: init(pgsphereExpr); @@ -692,19 +768,19 @@ public class PgSphereTranslator extends PostgreSQLTranslator { * * @throws ParseException If the angle syntax is wrong or not supported. */ - private double parseAngle() throws ParseException{ + private double parseAngle() throws ParseException { int oldPos = pos; String number = nextToken(); - try{ + try { double degrees = Double.parseDouble(number); int sign = (degrees < 0) ? -1 : 1; degrees = Math.abs(degrees); oldPos = pos; - try{ + try { if (nextToken().length() == 1 && token.charAt(0) == HOUR) sign *= 15; - else if (token.length() != 1 || token.charAt(0) != DEGREE){ + else if (token.length() != 1 || token.charAt(0) != DEGREE) { degrees = degrees * 180 / Math.PI; pos -= token.length(); return degrees * sign; @@ -714,10 +790,10 @@ public class PgSphereTranslator extends PostgreSQLTranslator { number = nextToken(); if (nextToken().length() == 1 && token.charAt(0) == MINUTE) degrees += Double.parseDouble(number) / 60; - else if (token.length() == 1 && token.charAt(0) == SECOND){ + else if (token.length() == 1 && token.charAt(0) == SECOND) { degrees += Double.parseDouble(number) / 3600; return degrees * sign; - }else{ + } else { pos = oldPos; return degrees * sign; } @@ -728,13 +804,13 @@ public class PgSphereTranslator extends PostgreSQLTranslator { degrees += Double.parseDouble(number) / 3600; else pos = oldPos; - }catch(EOEException ex){ + } catch(EOEException ex) { pos = oldPos; } return degrees * sign; - }catch(NumberFormatException nfe){ + } catch(NumberFormatException nfe) { throw new ParseException("Incorrect numeric syntax: \"" + number + "\"!", new TextPosition(1, pos - token.length(), 1, pos)); } } diff --git a/test/adql/translator/TestPgSphereTranslator.java b/test/adql/translator/TestPgSphereTranslator.java index 825f21a..0f48b09 100644 --- a/test/adql/translator/TestPgSphereTranslator.java +++ b/test/adql/translator/TestPgSphereTranslator.java @@ -19,6 +19,7 @@ import org.postgresql.util.PGobject; import adql.db.DBType; import adql.db.DBType.DBDatatype; import adql.db.STCS.Region; +import adql.parser.ADQLParser; import adql.parser.grammar.ParseException; import adql.query.operand.NumericConstant; import adql.query.operand.StringConstant; @@ -30,31 +31,35 @@ import adql.query.operand.function.geometry.GeometryFunction.GeometryValue; public class TestPgSphereTranslator { @BeforeClass - public static void setUpBeforeClass() throws Exception{} + public static void setUpBeforeClass() throws Exception { + } @AfterClass - public static void tearDownAfterClass() throws Exception{} + public static void tearDownAfterClass() throws Exception { + } @Before - public void setUp() throws Exception{} + public void setUp() throws Exception { + } @After - public void tearDown() throws Exception{} + public void tearDown() throws Exception { + } @Test - public void testTranslateCentroidFunction(){ - try{ + public void testTranslateCentroidFunction() { + try { PgSphereTranslator translator = new PgSphereTranslator(); CentroidFunction centfc = new CentroidFunction(new GeometryValue<GeometryFunction>(new CircleFunction(new StringConstant("ICRS"), new NumericConstant(128.23), new NumericConstant(0.53), new NumericConstant(2)))); assertEquals("center(scircle(spoint(radians(128.23),radians(0.53)),radians(2)))", translator.translate(centfc)); - }catch(Throwable t){ + } catch(Throwable t) { t.printStackTrace(System.err); fail("An error occured while building a simple CentroidFunction! (see the console for more details)"); } } @Test - public void testConvertTypeFromDB(){ + public void testConvertTypeFromDB() { PgSphereTranslator translator = new PgSphereTranslator(); // POINT @@ -83,7 +88,7 @@ public class TestPgSphereTranslator { } @Test - public void testConvertTypeToDB(){ + public void testConvertTypeToDB() { PgSphereTranslator translator = new PgSphereTranslator(); // NULL @@ -97,20 +102,20 @@ public class TestPgSphereTranslator { } @Test - public void testTranslateGeometryFromDB(){ + public void testTranslateGeometryFromDB() { PgSphereTranslator translator = new PgSphereTranslator(); PGobject pgo = new PGobject(); // NULL - try{ + try { assertNull(translator.translateGeometryFromDB(null)); - }catch(Throwable t){ + } catch(Throwable t) { t.printStackTrace(); fail(t.getMessage()); } // SPOINT - try{ + try { pgo.setType("spoint"); pgo.setValue("(0.1 , 0.2)"); Region r = translator.translateGeometryFromDB(pgo); @@ -131,13 +136,13 @@ public class TestPgSphereTranslator { r = translator.translateGeometryFromDB(pgo); assertEquals(5.72957, r.coordinates[0][0], 1e-5); assertEquals(11.45915, r.coordinates[0][1], 1e-5); - }catch(Throwable t){ + } catch(Throwable t) { t.printStackTrace(); fail(t.getMessage()); } // SCIRCLE - try{ + try { pgo.setType("scircle"); pgo.setValue("<(0.1,-0.2),1>"); Region r = translator.translateGeometryFromDB(pgo); @@ -162,13 +167,13 @@ public class TestPgSphereTranslator { assertEquals(5.72957, r.coordinates[0][0], 1e-5); assertEquals(-11.45915, r.coordinates[0][1], 1e-5); assertEquals(57.29577, r.radius, 1e-5); - }catch(Throwable t){ + } catch(Throwable t) { t.printStackTrace(); fail(t.getMessage()); } // SBOX - try{ + try { pgo.setType("sbox"); pgo.setValue("((0.1,0.2),(0.5,0.5))"); Region r = translator.translateGeometryFromDB(pgo); @@ -197,13 +202,13 @@ public class TestPgSphereTranslator { assertEquals(20.05352, r.coordinates[0][1], 1e-5); assertEquals(22.91831, r.width, 1e-5); assertEquals(17.18873, r.height, 1e-5); - }catch(Throwable t){ + } catch(Throwable t) { t.printStackTrace(); fail(t.getMessage()); } // SPOLY - try{ + try { pgo.setType("spoly"); pgo.setValue("{(0.789761486527434 , 0.00436332312998582),(0.789761486527434 , 0.00872664625997165),(0.785398163397448 , 0.00872664625997165),(0.785398163397448 , 0.00436332312998582),(0.781034840267463 , 0.00436332312998582),(0.781034840267463 , 0),(0.785398163397448 , 0)}"); Region r = translator.translateGeometryFromDB(pgo); @@ -272,47 +277,47 @@ public class TestPgSphereTranslator { assertEquals(0, r.coordinates[5][1], 1e-2); assertEquals(45, r.coordinates[6][0], 1e-2); assertEquals(0, r.coordinates[6][1], 1e-2); - }catch(Throwable t){ + } catch(Throwable t) { t.printStackTrace(); fail(t.getMessage()); } // OTHER - try{ + try { translator.translateGeometryFromDB(new Double(12.3)); fail("The translation of a Double as a geometry is not supported!"); - }catch(Throwable t){ + } catch(Throwable t) { assertTrue(t instanceof ParseException); assertEquals("Incompatible type! The column value \"12.3\" was supposed to be a geometrical object.", t.getMessage()); } - try{ + try { pgo.setType("sline"); pgo.setValue("( -90d, -20d, 200d, XYZ ), 30d "); translator.translateGeometryFromDB(pgo); fail("The translation of a sline is not supported!"); - }catch(Throwable t){ + } catch(Throwable t) { assertTrue(t instanceof ParseException); assertEquals("Unsupported PgSphere type: \"sline\"! Impossible to convert the column value \"( -90d, -20d, 200d, XYZ ), 30d \" into a Region.", t.getMessage()); } } @Test - public void testTranslateGeometryToDB(){ + public void testTranslateGeometryToDB() { PgSphereTranslator translator = new PgSphereTranslator(); - try{ + try { // NULL assertNull(translator.translateGeometryToDB(null)); // POSITION - Region r = new Region(null, new double[]{45,0}); + Region r = new Region(null, new double[]{ 45, 0 }); PGobject pgo = (PGobject)translator.translateGeometryToDB(r); assertNotNull(pgo); assertEquals("spoint", pgo.getType()); assertEquals("(45.0d,0.0d)", pgo.getValue()); // CIRCLE - r = new Region(null, new double[]{45,0}, 1.2); + r = new Region(null, new double[]{ 45, 0 }, 1.2); pgo = (PGobject)translator.translateGeometryToDB(r); assertNotNull(pgo); assertEquals("spoly", pgo.getType()); @@ -320,33 +325,66 @@ public class TestPgSphereTranslator { assertEquals("{(46.2d,0.0d),(46.17694233d,0.23410838d),(46.10865543d,0.45922011d),(45.99776353d,0.66668427d),(45.84852813d,0.84852813d),(45.66668427d,0.99776353d),(45.45922011d,1.10865543d),(45.23410838d,1.17694233d),(45.0d,1.2d),(44.76589161d,1.17694233d),(44.54077988d,1.10865543d),(44.33331572d,0.99776353d),(44.15147186d,0.84852813d),(44.00223646d,0.66668427d),(43.89134456d,0.45922011d),(43.82305766d,0.23410838d),(43.8d,-9.188564877424678E-16d),(43.82305766d,-0.23410838d),(43.89134456d,-0.45922011d),(44.00223646d,-0.66668427d),(44.15147186d,-0.84852813d),(44.33331572d,-0.99776353d),(44.54077988d,-1.10865543d),(44.76589161d,-1.17694233d),(45.0d,-1.2d),(45.23410838d,-1.17694233d),(45.45922011d,-1.10865543d),(45.66668427d,-0.99776353d),(45.84852813d,-0.84852813d),(45.99776353d,-0.66668427d),(46.10865543d,-0.45922011d),(46.17694233d,-0.23410838d)}", fp8.matcher(pgo.getValue()).replaceAll("$1d")); // BOX - r = new Region(null, new double[]{45,0}, 1.2, 5); + r = new Region(null, new double[]{ 45, 0 }, 1.2, 5); pgo = (PGobject)translator.translateGeometryToDB(r); assertNotNull(pgo); assertEquals("spoly", pgo.getType()); assertEquals("{(44.4d,-2.5d),(44.4d,2.5d),(45.6d,2.5d),(45.6d,-2.5d)}", pgo.getValue()); // POLYGON - r = new Region(null, new double[][]{new double[]{45.25,0.25},new double[]{45.25,0.5},new double[]{45,0.5},new double[]{45,0.25},new double[]{44.75,0.25},new double[]{44.75,0},new double[]{45,0}}); + r = new Region(null, new double[][]{ new double[]{ 45.25, 0.25 }, new double[]{ 45.25, 0.5 }, new double[]{ 45, 0.5 }, new double[]{ 45, 0.25 }, new double[]{ 44.75, 0.25 }, new double[]{ 44.75, 0 }, new double[]{ 45, 0 } }); pgo = (PGobject)translator.translateGeometryToDB(r); assertNotNull(pgo); assertEquals("spoly", pgo.getType()); assertEquals("{(45.25d,0.25d),(45.25d,0.5d),(45.0d,0.5d),(45.0d,0.25d),(44.75d,0.25d),(44.75d,0.0d),(45.0d,0.0d)}", pgo.getValue()); // OTHER - try{ - r = new Region(new Region(null, new double[]{45,0})); + try { + r = new Region(new Region(null, new double[]{ 45, 0 })); translator.translateGeometryToDB(r); fail("The translation of a STC Not region is not supported!"); - }catch(Throwable ex){ + } catch(Throwable ex) { assertTrue(ex instanceof ParseException); assertEquals("Unsupported geometrical region: \"" + r.type + "\"!", ex.getMessage()); } - }catch(ParseException t){ + } catch(ParseException t) { t.printStackTrace(); fail(t.getMessage()); } } + @Test + public void testTranslateXMatch() { + PgSphereTranslator translator = new PgSphereTranslator(); + ADQLParser parser = new ADQLParser(); + + try { + // CASE: CONTAINS(POINT, CIRCLE) = 1 + assertEquals("(spoint(radians(ra),radians(dec)) @ scircle(spoint(radians(0),radians(0)),radians(1.))) = '1'", translator.translate(parser.parseWhere("WHERE CONTAINS(POINT('', ra, dec), CIRCLE('', 0, 0, 1.)) = 1").get(0))); + + // CASE: 1 = CONTAINS(POINT, CIRCLE) + assertEquals("'1' = (spoint(radians(ra),radians(dec)) @ scircle(spoint(radians(0),radians(0)),radians(1.)))", translator.translate(parser.parseWhere("WHERE 1=CONTAINS(POINT('', ra, dec), CIRCLE('', 0, 0, 1.))").get(0))); + + // CASE: DISTANCE(...) <= 1 + assertEquals("((spoint(radians(ra),radians(dec)) @ scircle(spoint(radians(0),radians(0)),radians(1.))) = '1')", translator.translate(parser.parseWhere("WHERE DISTANCE(POINT('', ra, dec), POINT('', 0, 0)) <= 1.").get(0))); + + // CASE: DISTANCE(...) >= 1 + assertEquals("((spoint(radians(ra),radians(dec)) @ scircle(spoint(radians(0),radians(0)),radians(1.))) = '0')", translator.translate(parser.parseWhere("WHERE DISTANCE(POINT('', ra, dec), POINT('', 0, 0)) >= 1.").get(0))); + + // CASE: DISTANCE(...) < 1 + assertEquals("((spoint(radians(ra),radians(dec)) @ scircle(spoint(radians(0),radians(0)),radians(1.))) = '1' AND degrees(spoint(radians(ra),radians(dec)) <-> spoint(radians(0),radians(0))) < 1.)", translator.translate(parser.parseWhere("WHERE DISTANCE(POINT('', ra, dec), POINT('', 0, 0)) < 1.").get(0))); + + // CASE: DISTANCE(...) > 1 + assertEquals("((spoint(radians(ra),radians(dec)) @ scircle(spoint(radians(0),radians(0)),radians(1.))) = '0' AND degrees(spoint(radians(ra),radians(dec)) <-> spoint(radians(0),radians(0))) > 1.)", translator.translate(parser.parseWhere("WHERE DISTANCE(POINT('', ra, dec), POINT('', 0, 0)) > 1.").get(0))); + + } catch(ParseException pe) { + pe.printStackTrace(); + fail("Failed parsing before translation!"); + } catch(Exception ex) { + ex.printStackTrace(); + fail("Unexpected failure of xmatch translation! (see console for more details)"); + } + } + } -- GitLab