Skip to content
Snippets Groups Projects
Commit 046ac691 authored by Grégory Mantelet's avatar Grégory Mantelet
Browse files

[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.
parent 5cc544e4
No related branches found
No related tags found
No related merge requests found
......@@ -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&eacute;gory Mantelet (CDS;ARI)
* @version 1.4 (07/2017)
* @version 2.0 (08/2019)
*/
public class PgSphereTranslator extends PostgreSQLTranslator {
......@@ -201,14 +210,80 @@ public class PgSphereTranslator extends PostgreSQLTranslator {
@Override
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 &lt;, &le;, &gt; or &ge;.</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) {
// If no type is provided return VARCHAR:
......@@ -427,7 +502,8 @@ public class PgSphereTranslator extends PostgreSQLTranslator {
/**
* Build the PgSphere parser.
*/
public PgSphereGeometryParser(){}
public PgSphereGeometryParser() {
}
/**
* Prepare the parser in order to read the given PgSphere expression.
......
......@@ -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,16 +31,20 @@ 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() {
......@@ -349,4 +354,37 @@ public class TestPgSphereTranslator {
}
}
@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)");
}
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment