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

[ADQL] Move validation of coord. sys. and STC-s expressions from DBChecker to

ADQLParser.
parent e4366686
No related branches found
No related tags found
No related merge requests found
...@@ -77,14 +77,14 @@ import adql.search.SimpleSearchHandler; ...@@ -77,14 +77,14 @@ import adql.search.SimpleSearchHandler;
* query</li> * query</li>
* <li>Resolve all unknown functions as supported User Defined Functions * <li>Resolve all unknown functions as supported User Defined Functions
* (UDFs)</li> * (UDFs)</li>
* <li>Check whether all used coordinate systems are supported</li>
* <li>Check that types of columns and UDFs match with their context</li> * <li>Check that types of columns and UDFs match with their context</li>
* </ol> * </ol>
* *
* <p><i><b>IMPORTANT note:</b> * <p><i><b>IMPORTANT note:</b>
* Since v2.0, the check of supported geometrical functions is performed * Since v2.0, the check of supported geometric functions, STC-s expressions
* directly in ADQLParser through the notion of Optional Features. * and coordinate systems are performed automatically in
* The declaration of supported geometrical functions must now be done * {@link adql.parser.ADQLParser ADQLParser} through the notion of Optional
* Features. The declaration of supported geometric functions must now be done
* with {@link adql.parser.ADQLParser#getSupportedFeatures() ADQLParser.getSupportedFeatures()} * with {@link adql.parser.ADQLParser#getSupportedFeatures() ADQLParser.getSupportedFeatures()}
* (see also {@link adql.parser.feature.FeatureSet FeatureSet}). * (see also {@link adql.parser.feature.FeatureSet FeatureSet}).
* </i></p> * </i></p>
...@@ -108,11 +108,11 @@ import adql.search.SimpleSearchHandler; ...@@ -108,11 +108,11 @@ import adql.search.SimpleSearchHandler;
* particularly useful for the translation of the ADQL query to SQL, because * particularly useful for the translation of the ADQL query to SQL, because
* the ADQL name of columns and tables can be replaced in SQL by their DB name, * the ADQL name of columns and tables can be replaced in SQL by their DB name,
* if different. This mapping is done automatically by * if different. This mapping is done automatically by
* {@link adql.translator.JDBCTranslator}. * {@link adql.translator.JDBCTranslator JDBCTranslator}.
* </i></p> * </i></p>
* *
* @author Gr&eacute;gory Mantelet (CDS;ARI) * @author Gr&eacute;gory Mantelet (CDS;ARI)
* @version 2.0 (07/2019) * @version 2.0 (08/2019)
*/ */
public class DBChecker implements QueryChecker { public class DBChecker implements QueryChecker {
...@@ -148,13 +148,19 @@ public class DBChecker implements QueryChecker { ...@@ -148,13 +148,19 @@ public class DBChecker implements QueryChecker {
* However, if not, all items of this list must be the only allowed coordinate systems. * However, if not, all items of this list must be the only allowed coordinate systems.
* So, if the list is empty, none is allowed. * So, if the list is empty, none is allowed.
* </p> * </p>
* @since 1.3 */ * @since 1.3
* @deprecated Since v2.0, supported coordinate systems must be declared
* in ADQLParser. */
@Deprecated
protected String[] allowedCoordSys = null; protected String[] allowedCoordSys = null;
/** <p>A regular expression built using the list of allowed coordinate systems. /** <p>A regular expression built using the list of allowed coordinate systems.
* With this regex, it is possible to known whether a coordinate system expression is allowed or not.</p> * With this regex, it is possible to known whether a coordinate system expression is allowed or not.</p>
* <p>If NULL, all coordinate systems are allowed.</p> * <p>If NULL, all coordinate systems are allowed.</p>
* @since 1.3 */ * @since 1.3
* @deprecated Since v2.0, supported coordinate systems must be declared
* in ADQLParser. */
@Deprecated
protected String coordSysRegExp = null; protected String coordSysRegExp = null;
/** <p>List of all allowed User Defined Functions (UDFs).</p> /** <p>List of all allowed User Defined Functions (UDFs).</p>
...@@ -341,7 +347,13 @@ public class DBChecker implements QueryChecker { ...@@ -341,7 +347,13 @@ public class DBChecker implements QueryChecker {
* @return A sorted array containing all - except NULL and empty strings - items of the given collection. * @return A sorted array containing all - except NULL and empty strings - items of the given collection.
* *
* @since 1.3 * @since 1.3
*
* @deprecated Since v2.0, this tool function is no longer used. It was
* useful only to collect allowed geometries and coordinate
* systems....but these are now checked by
* {@link adql.parser.ADQLParser ADQLParser}.
*/ */
@Deprecated
protected final static String[] specialSort(final Collection<String> items) { protected final static String[] specialSort(final Collection<String> items) {
// Nothing to do if the array is NULL: // Nothing to do if the array is NULL:
if (items == null) if (items == null)
...@@ -392,16 +404,18 @@ public class DBChecker implements QueryChecker { ...@@ -392,16 +404,18 @@ public class DBChecker implements QueryChecker {
/* CHECK METHODS */ /* CHECK METHODS */
/* ************* */ /* ************* */
/** /**
* <p>Check all the columns, tables and UDFs references inside the given query.</p> * Check all the columns, tables and UDFs references inside the given query.
* *
* <p><i> * <p><i><b>Note:</b>
* <u>Note:</u> This query has already been parsed ; thus it is already syntactically correct. * This query has already been parsed ; thus it is already syntactically
* Only the consistency with the published tables, columns and all the defined UDFs must be checked. * correct. Only the consistency with the published tables, columns and all
* the defined UDFs must be checked.
* </i></p> * </i></p>
* *
* @param query The query to check. * @param query The query to check.
* *
* @throws ParseException An {@link UnresolvedIdentifiersException} if some tables or columns can not be resolved. * @throws ParseException An {@link UnresolvedIdentifiersException} if
* some tables or columns can not be resolved.
* *
* @see #check(ADQLQuery, Stack) * @see #check(ADQLQuery, Stack)
*/ */
...@@ -411,31 +425,37 @@ public class DBChecker implements QueryChecker { ...@@ -411,31 +425,37 @@ public class DBChecker implements QueryChecker {
} }
/** /**
* <p>Process several (semantic) verifications in the given ADQL query.</p> * Process several (semantic) verifications in the given ADQL query.
* *
* <p>Main verifications done in this function:</p> * <p>Main verifications done in this function:</p>
* <ol> * <ol>
* <li>Existence of DB items (tables and columns)</li> * <li>Existence of DB items (tables and columns)</li>
* <li>Semantic verification of sub-queries</li> * <li>Semantic verification of sub-queries</li>
* <li>Support of every encountered User Defined Functions (UDFs - functions unknown by the syntactic parser)</li> * <li>Support of every encountered User Defined Functions (UDFs -
* <li>Support of every encountered geometries (functions, coordinate systems and STC-S expressions)</li> * functions unknown by the syntactic parser)</li>
* <li>Consistency of types still unknown (because the syntactic parser could not yet resolve them)</li> * <li>Consistency of types still unknown (because the syntactic parser
* could not yet resolve them)</li>
* </ol> * </ol>
* *
* @param query The query to check. * @param query The query to check.
* @param fathersList List of all columns available in the father queries and that should be accessed in sub-queries. * @param fathersList List of all columns available in the father queries
* Each item of this stack is a list of columns available in each father-level query. * and that should be accessed in sub-queries. Each
* <i>Note: this parameter is NULL if this function is called with the root/father query as parameter.</i> * item of this stack is a list of columns available in
* * each father-level query. <i><b>Note:</b> this
* @throws UnresolvedIdentifiersException An {@link UnresolvedIdentifiersException} if one or several of the above listed tests have detected * parameter is NULL if this function is called with
* some semantic errors (i.e. unresolved table, columns, function). * the root/father query as parameter.</i>
*
* @throws UnresolvedIdentifiersException An {@link UnresolvedIdentifiersException}
* if one or several of the above
* listed tests have detected some
* semantic errors (i.e. unresolved
* table, columns, function).
* *
* @since 1.2 * @since 1.2
* *
* @see #checkDBItems(ADQLQuery, Stack, UnresolvedIdentifiersException) * @see #checkDBItems(ADQLQuery, Stack, UnresolvedIdentifiersException)
* @see #checkSubQueries(ADQLQuery, Stack, SearchColumnList, UnresolvedIdentifiersException) * @see #checkSubQueries(ADQLQuery, Stack, SearchColumnList, UnresolvedIdentifiersException)
* @see #checkUDFs(ADQLQuery, UnresolvedIdentifiersException) * @see #checkUDFs(ADQLQuery, UnresolvedIdentifiersException)
* @see #checkGeometries(ADQLQuery, UnresolvedIdentifiersException)
* @see #checkTypes(ADQLQuery, UnresolvedIdentifiersException) * @see #checkTypes(ADQLQuery, UnresolvedIdentifiersException)
*/ */
protected void check(final ADQLQuery query, final Stack<SearchColumnList> fathersList) throws UnresolvedIdentifiersException { protected void check(final ADQLQuery query, final Stack<SearchColumnList> fathersList) throws UnresolvedIdentifiersException {
...@@ -448,13 +468,10 @@ public class DBChecker implements QueryChecker { ...@@ -448,13 +468,10 @@ public class DBChecker implements QueryChecker {
if (allowedUdfs != null) if (allowedUdfs != null)
checkUDFs(query, errors); checkUDFs(query, errors);
// C. Check geometries: // C. Check types:
checkGeometries(query, errors);
// D. Check types:
checkTypes(query, errors); checkTypes(query, errors);
// E. Check sub-queries: // D. Check sub-queries:
checkSubQueries(query, fathersList, availableColumns, errors); checkSubQueries(query, fathersList, availableColumns, errors);
// Throw all errors, if any: // Throw all errors, if any:
...@@ -1014,7 +1031,16 @@ public class DBChecker implements QueryChecker { ...@@ -1014,7 +1031,16 @@ public class DBChecker implements QueryChecker {
* @see #resolveSTCSExpressions(ADQLQuery, BinarySearch, UnresolvedIdentifiersException) * @see #resolveSTCSExpressions(ADQLQuery, BinarySearch, UnresolvedIdentifiersException)
* *
* @since 1.3 * @since 1.3
*
* @deprecated Since 2.0, validation of the geometric functions is
* performed automatically by
* {@link adql.parser.ADQLParser ADQLParser}. Geometric
* functions are optional features and should be declared as
* such in the {@link adql.parser.ADQLParser ADQLParser} if
* they are supported (see
* {@link adql.parser.ADQLParser#getSupportedFeatures() ADQLParser.getSupportedFeatures()}).
*/ */
@Deprecated
protected void checkGeometries(final ADQLQuery query, final UnresolvedIdentifiersException errors) { protected void checkGeometries(final ADQLQuery query, final UnresolvedIdentifiersException errors) {
BinarySearch<String, String> binSearch = new BinarySearch<String, String>() { BinarySearch<String, String> binSearch = new BinarySearch<String, String>() {
@Override @Override
...@@ -1041,10 +1067,13 @@ public class DBChecker implements QueryChecker { ...@@ -1041,10 +1067,13 @@ public class DBChecker implements QueryChecker {
* @see #checkGeometryFunction(String, ADQLFunction, BinarySearch, UnresolvedIdentifiersException) * @see #checkGeometryFunction(String, ADQLFunction, BinarySearch, UnresolvedIdentifiersException)
* *
* @since 1.3 * @since 1.3
* @deprecated No more used since v2.0. Check of the geometric functions is *
* now performed directly in ADQLParser. Geometric functions * @deprecated Since 2.0, validation of the geometric functions is
* are optional features and should be declared as such in the * performed automatically by
* ADQLParser if they are supported (see * {@link adql.parser.ADQLParser ADQLParser}. Geometric
* functions are optional features and should be declared as
* such in the {@link adql.parser.ADQLParser ADQLParser} if
* they are supported (see
* {@link adql.parser.ADQLParser#getSupportedFeatures() ADQLParser.getSupportedFeatures()}). * {@link adql.parser.ADQLParser#getSupportedFeatures() ADQLParser.getSupportedFeatures()}).
*/ */
@Deprecated @Deprecated
...@@ -1076,10 +1105,13 @@ public class DBChecker implements QueryChecker { ...@@ -1076,10 +1105,13 @@ public class DBChecker implements QueryChecker {
* @param errors List of errors to complete in this function each time a geometrical function is not supported. * @param errors List of errors to complete in this function each time a geometrical function is not supported.
* *
* @since 1.3 * @since 1.3
* @deprecated No more used since v2.0. Check of the geometric functions is *
* now performed directly in ADQLParser. Geometric functions * @deprecated Since 2.0, validation of the geometric functions is
* are optional features and should be declared as such in the * performed automatically by
* ADQLParser if they are supported (see * {@link adql.parser.ADQLParser ADQLParser}. Geometric
* functions are optional features and should be declared as
* such in the {@link adql.parser.ADQLParser ADQLParser} if
* they are supported (see
* {@link adql.parser.ADQLParser#getSupportedFeatures() ADQLParser.getSupportedFeatures()}). * {@link adql.parser.ADQLParser#getSupportedFeatures() ADQLParser.getSupportedFeatures()}).
*/ */
@Deprecated @Deprecated
...@@ -1106,7 +1138,11 @@ public class DBChecker implements QueryChecker { ...@@ -1106,7 +1138,11 @@ public class DBChecker implements QueryChecker {
* @see #checkCoordinateSystem(StringConstant, UnresolvedIdentifiersException) * @see #checkCoordinateSystem(StringConstant, UnresolvedIdentifiersException)
* *
* @since 1.3 * @since 1.3
*
* @deprecated Since 2.0, the validation of coordinate systems is performed
* automatically by {@link adql.parser.ADQLParser ADQLParser}.
*/ */
@Deprecated
protected void resolveCoordinateSystems(final ADQLQuery query, final UnresolvedIdentifiersException errors) { protected void resolveCoordinateSystems(final ADQLQuery query, final UnresolvedIdentifiersException errors) {
ISearchHandler sHandler = new SearchCoordSysHandler(); ISearchHandler sHandler = new SearchCoordSysHandler();
sHandler.search(query); sHandler.search(query);
...@@ -1124,7 +1160,11 @@ public class DBChecker implements QueryChecker { ...@@ -1124,7 +1160,11 @@ public class DBChecker implements QueryChecker {
* @see #checkCoordinateSystem(adql.db.STCS.CoordSys, ADQLOperand, UnresolvedIdentifiersException) * @see #checkCoordinateSystem(adql.db.STCS.CoordSys, ADQLOperand, UnresolvedIdentifiersException)
* *
* @since 1.3 * @since 1.3
*
* @deprecated Since 2.0, the validation of coordinate systems is performed
* automatically by {@link adql.parser.ADQLParser ADQLParser}.
*/ */
@Deprecated
protected void checkCoordinateSystem(final StringConstant adqlCoordSys, final UnresolvedIdentifiersException errors) { protected void checkCoordinateSystem(final StringConstant adqlCoordSys, final UnresolvedIdentifiersException errors) {
String coordSysStr = adqlCoordSys.getValue(); String coordSysStr = adqlCoordSys.getValue();
try { try {
...@@ -1142,7 +1182,11 @@ public class DBChecker implements QueryChecker { ...@@ -1142,7 +1182,11 @@ public class DBChecker implements QueryChecker {
* @param errors List of errors to complete in this function each time a coordinate system is not supported. * @param errors List of errors to complete in this function each time a coordinate system is not supported.
* *
* @since 1.3 * @since 1.3
*
* @deprecated Since 2.0, the validation of coordinate systems is performed
* automatically by {@link adql.parser.ADQLParser ADQLParser}.
*/ */
@Deprecated
protected void checkCoordinateSystem(final CoordSys coordSys, final ADQLOperand operand, final UnresolvedIdentifiersException errors) { protected void checkCoordinateSystem(final CoordSys coordSys, final ADQLOperand operand, final UnresolvedIdentifiersException errors) {
if (coordSysRegExp != null && coordSys != null && !coordSys.toFullSTCS().matches(coordSysRegExp)) { if (coordSysRegExp != null && coordSys != null && !coordSys.toFullSTCS().matches(coordSysRegExp)) {
StringBuffer buf = new StringBuffer(); StringBuffer buf = new StringBuffer();
...@@ -1180,7 +1224,11 @@ public class DBChecker implements QueryChecker { ...@@ -1180,7 +1224,11 @@ public class DBChecker implements QueryChecker {
* @see #checkRegion(adql.db.STCS.Region, RegionFunction, BinarySearch, UnresolvedIdentifiersException) * @see #checkRegion(adql.db.STCS.Region, RegionFunction, BinarySearch, UnresolvedIdentifiersException)
* *
* @since 1.3 * @since 1.3
*
* @deprecated Since 2.0, the validation of STCs expressions is performed
* automatically by {@link adql.parser.ADQLParser ADQLParser}.
*/ */
@Deprecated
protected void resolveSTCSExpressions(final ADQLQuery query, final BinarySearch<String, String> binSearch, final UnresolvedIdentifiersException errors) { protected void resolveSTCSExpressions(final ADQLQuery query, final BinarySearch<String, String> binSearch, final UnresolvedIdentifiersException errors) {
// Search REGION functions: // Search REGION functions:
ISearchHandler sHandler = new SearchRegionHandler(); ISearchHandler sHandler = new SearchRegionHandler();
...@@ -1224,7 +1272,11 @@ public class DBChecker implements QueryChecker { ...@@ -1224,7 +1272,11 @@ public class DBChecker implements QueryChecker {
* @see #checkRegion(adql.db.STCS.Region, RegionFunction, BinarySearch, UnresolvedIdentifiersException) * @see #checkRegion(adql.db.STCS.Region, RegionFunction, BinarySearch, UnresolvedIdentifiersException)
* *
* @since 1.3 * @since 1.3
*
* @deprecated Since 2.0, the validation of REGIONs is performed
* automatically by {@link adql.parser.ADQLParser ADQLParser}.
*/ */
@Deprecated
protected void checkRegion(final Region r, final RegionFunction fct, final BinarySearch<String, String> binSearch, final UnresolvedIdentifiersException errors) { protected void checkRegion(final Region r, final RegionFunction fct, final BinarySearch<String, String> binSearch, final UnresolvedIdentifiersException errors) {
if (r == null) if (r == null)
return; return;
...@@ -1565,7 +1617,10 @@ public class DBChecker implements QueryChecker { ...@@ -1565,7 +1617,10 @@ public class DBChecker implements QueryChecker {
* @author Gr&eacute;gory Mantelet (ARI) * @author Gr&eacute;gory Mantelet (ARI)
* @version 1.3 (10/2014) * @version 1.3 (10/2014)
* @since 1.3 * @since 1.3
* @deprecated Since 2.0, the validation of REGIONs is performed
* automatically by {@link adql.parser.ADQLParser ADQLParser}.
*/ */
@Deprecated
private static class SearchCoordSysHandler extends SimpleSearchHandler { private static class SearchCoordSysHandler extends SimpleSearchHandler {
@Override @Override
protected boolean match(ADQLObject obj) { protected boolean match(ADQLObject obj) {
...@@ -1588,7 +1643,10 @@ public class DBChecker implements QueryChecker { ...@@ -1588,7 +1643,10 @@ public class DBChecker implements QueryChecker {
* @author Gr&eacute;gory Mantelet (ARI) * @author Gr&eacute;gory Mantelet (ARI)
* @version 1.3 (10/2014) * @version 1.3 (10/2014)
* @since 1.3 * @since 1.3
* @deprecated Since 2.0, the validation of REGIONs is performed
* automatically by {@link adql.parser.ADQLParser ADQLParser}.
*/ */
@Deprecated
private static class SearchRegionHandler extends SimpleSearchHandler { private static class SearchRegionHandler extends SimpleSearchHandler {
@Override @Override
protected boolean match(ADQLObject obj) { protected boolean match(ADQLObject obj) {
......
...@@ -23,8 +23,14 @@ import java.io.ByteArrayInputStream; ...@@ -23,8 +23,14 @@ import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import adql.db.DBChecker; import adql.db.DBChecker;
import adql.db.STCS;
import adql.db.STCS.CoordSys;
import adql.db.STCS.Region;
import adql.db.STCS.RegionType;
import adql.db.exception.UnresolvedIdentifiersException; import adql.db.exception.UnresolvedIdentifiersException;
import adql.db.exception.UnsupportedFeatureException; import adql.db.exception.UnsupportedFeatureException;
import adql.parser.feature.FeatureSet; import adql.parser.feature.FeatureSet;
...@@ -36,6 +42,7 @@ import adql.parser.grammar.ADQLGrammar201; ...@@ -36,6 +42,7 @@ import adql.parser.grammar.ADQLGrammar201;
import adql.parser.grammar.ParseException; import adql.parser.grammar.ParseException;
import adql.parser.grammar.Token; import adql.parser.grammar.Token;
import adql.parser.grammar.TokenMgrError; import adql.parser.grammar.TokenMgrError;
import adql.query.ADQLIterator;
import adql.query.ADQLObject; import adql.query.ADQLObject;
import adql.query.ADQLOrder; import adql.query.ADQLOrder;
import adql.query.ADQLQuery; import adql.query.ADQLQuery;
...@@ -44,8 +51,18 @@ import adql.query.ClauseConstraints; ...@@ -44,8 +51,18 @@ import adql.query.ClauseConstraints;
import adql.query.ClauseSelect; import adql.query.ClauseSelect;
import adql.query.from.FromContent; import adql.query.from.FromContent;
import adql.query.operand.ADQLColumn; import adql.query.operand.ADQLColumn;
import adql.query.operand.ADQLOperand;
import adql.query.operand.StringConstant;
import adql.query.operand.function.geometry.BoxFunction;
import adql.query.operand.function.geometry.CircleFunction;
import adql.query.operand.function.geometry.ContainsFunction; import adql.query.operand.function.geometry.ContainsFunction;
import adql.query.operand.function.geometry.GeometryFunction;
import adql.query.operand.function.geometry.PointFunction;
import adql.query.operand.function.geometry.PolygonFunction;
import adql.query.operand.function.geometry.RegionFunction;
import adql.search.ISearchHandler;
import adql.search.SearchOptionalFeaturesHandler; import adql.search.SearchOptionalFeaturesHandler;
import adql.search.SimpleSearchHandler;
import adql.translator.PostgreSQLTranslator; import adql.translator.PostgreSQLTranslator;
import adql.translator.TranslationException; import adql.translator.TranslationException;
...@@ -82,11 +99,13 @@ import adql.translator.TranslationException; ...@@ -82,11 +99,13 @@ import adql.translator.TranslationException;
* <p> * <p>
* In the above example, the parser runs with the minimal set of options. It * In the above example, the parser runs with the minimal set of options. It
* means that only the default optional language features are available, any * means that only the default optional language features are available, any
* UDF (even if undeclared) is allowed and no consistency with a list of tables * UDF (even if undeclared) and any coordinate system are allowed and no
* and columns is performed. These points can be customized at creation with * consistency with a list of tables and columns is performed. These points can
* be customized at creation with
* {@link #ADQLParser(ADQLVersion, QueryChecker, ADQLQueryFactory, FeatureSet)} * {@link #ADQLParser(ADQLVersion, QueryChecker, ADQLQueryFactory, FeatureSet)}
* but also after creation with {@link #setSupportedFeatures(FeatureSet)} and * but also after creation with {@link #setSupportedFeatures(FeatureSet)},
* {@link #setQueryChecker(QueryChecker)}. * {@link #setQueryChecker(QueryChecker)} and
* {@link #setAllowedCoordSys(Collection)}.
* </p> * </p>
* *
* <h3>Runnable class</h3> * <h3>Runnable class</h3>
...@@ -137,15 +156,37 @@ import adql.translator.TranslationException; ...@@ -137,15 +156,37 @@ import adql.translator.TranslationException;
* features are supported or not ; all optional features used in the query * features are supported or not ; all optional features used in the query
* while being declared as un-supported will throw an error at the end of * while being declared as un-supported will throw an error at the end of
* the parsing</li> * the parsing</li>
* <li>{@link #setAllowedCoordSys(Collection)} to set which coordinate systems
* are allowed when specifying a geometric region (e.g. POINT, CIRCLE,
* REGION) ; <i><b>note:</b> this function is mainly useful with ADQL-2.0
* because it is the only version in which the coordinate system parameter
* is mandatory</i></li>
* </ul> * </ul>
* *
* <h3>Default general checks</h3>
*
* <p>
* This ADQL parser always performs some verification after the parsing of an
* ADQL query. In addition of the syntax, this parser also ensures that no
* unsupported optional language feature and no unsupported coordinate system
* are used in parsed ADQL queries. If unsupported content is detected, a
* {@link ParseException} is immediately raised.
* </p>
*
* <p><i><b>Note:</b>
* By default, all optional language features are supported, and any UDF and
* coordinate system are allowed.
* </i></p>
*
*
* <h3>Custom checks</h3> * <h3>Custom checks</h3>
* *
* <p> * <p>
* This parser is able, thanks to a {@link QueryChecker} object, to check each * Besides the general checks, this parser allows the addition of a custom
* {@link ADQLQuery} just after its generation. It could be used, for * validation. Thanks to a {@link QueryChecker} object, it is able to check
* instance, to check the consistency between the ADQL query to parse and the * each {@link ADQLQuery} just after its generation and the general checks.
* "database" on which the query must be executed. * It could be used, for instance, to check the consistency between the ADQL
* query to parse and the "database" on which the query must be executed.
* </p> * </p>
* *
* <p> * <p>
...@@ -196,6 +237,35 @@ public class ADQLParser { ...@@ -196,6 +237,35 @@ public class ADQLParser {
* <p><i><b>Implementation note:</b> Never NULL.</i></p> */ * <p><i><b>Implementation note:</b> Never NULL.</i></p> */
protected FeatureSet supportedFeatures; protected FeatureSet supportedFeatures;
/** List of all allowed coordinate systems.
* <p>
* Each item of this list must be of the form: "{frame} {refpos} {flavor}".
* Each of these 3 items can be either of value, a list of values expressed
* with the syntax "({value1}|{value2}|...)" or a '*' to mean all possible
* values.
* </p>
* <p><i><b>Note:</b>
* since a default value (corresponding to the empty string - '') should
* always be possible for each part of a coordinate system, the checker
* will always add the default value (UNKNOWNFRAME, UNKNOWNREFPOS or
* SPHERICAL2) into the given list of possible values for each coord. sys.
* part.
* </i></p>
* <p>
* If this list is NULL, all coordinates systems are allowed.
* However, if not, all items of this list must be the only allowed
* coordinate systems. So, if the list is empty, none is allowed.
* </p> */
protected String[] allowedCoordSys = null;
/** A regular expression built using the list of allowed coordinate systems.
* <p>
* With this regex, it is possible to known whether a coordinate system
* expression is allowed or not.
* </p>
* <p>If NULL, all coordinate systems are allowed.</p> */
protected String coordSysRegExp = null;
/** API to check {@link ADQLQuery ADQL queries} (sub-queries or not) just /** API to check {@link ADQLQuery ADQL queries} (sub-queries or not) just
* after their generation. * after their generation.
* <p><i><b>Note:</b> * <p><i><b>Note:</b>
...@@ -547,6 +617,60 @@ public class ADQLParser { ...@@ -547,6 +617,60 @@ public class ADQLParser {
supportedFeatures = features; supportedFeatures = features;
} }
/**
* Get the list of allowed coordinate systems.
*
* <p>
* If NULL, any coordinate system is allowed.
* But if empty array, no coordinate system is allowed.
* </p>
*
* @return All allowed coordinate systems.
*/
public final String[] getAllowedCoordSys() {
return allowedCoordSys;
}
/**
* Set the list of allowed coordinate systems.
*
* <p>
* Each item of this list must be of the form: "{frame} {refpos} {flavor}".
* Each of these 3 items can be either of value, a list of values expressed
* with the syntax "({value1}|{value2}|...)" or a '*' to mean all possible
* values.
* </p>
*
* <p><i><b>Note:</b>
* since a default value (corresponding to the empty string - '') should
* always be possible for each part of a coordinate system, this parser
* will always add the default value (UNKNOWNFRAME, UNKNOWNREFPOS or
* SPHERICAL2) into the given list of possible values for each coord. sys.
* part.
* </i></p>
*
* <p>
* If this list is NULL, all coordinates systems are allowed.
* However, if not, all items of this list must be the only allowed
* coordinate systems. So, if the list is empty, none is allowed.
* </p>
*
* <p><i><b>Note:</b>
* When an exception is thrown, the list of allowed coordinate systems of
* this parser stays in the same state than before calling this function.
* </i></p>
*
* @param allowedCoordSys List of allowed coordinate systems.
*
* @throws ParseException If the syntax of one of the given coordinate
* system is incorrect.
*/
public final void setAllowedCoordSys(final Collection<String> allowedCoordSys) throws ParseException {
String[] tempAllowedCoordSys = specialSort(allowedCoordSys);
coordSysRegExp = STCS.buildCoordSysRegExp(tempAllowedCoordSys);
this.allowedCoordSys = tempAllowedCoordSys;
}
/** /**
* Get the custom checker of parsed ADQL queries. * Get the custom checker of parsed ADQL queries.
* *
...@@ -681,50 +805,6 @@ public class ADQLParser { ...@@ -681,50 +805,6 @@ public class ADQLParser {
return parsedQuery; return parsedQuery;
} }
/**
* Run the general and common checks on the given ADQL tree.
*
* <p>
* By default, this function only checks whether or not language features
* found in the given ADQL tree are supported. If not, a
* {@link ParseException} is thrown.
* </p>
*
* <p><i><b>Implementation note:</b>
* By default, when a part of the ADQL tree uses an unsupported language
* feature, an {@link UnsupportedFeatureException} is created. This
* function does not stop at the first encountered unsupported feature. It
* keeps reading the ADQL tree and appends all created
* {@link UnsupportedFeatureException}s into an
* {@link UnresolvedIdentifiersException}. Finally, when the tree has been
* entirely read and if unsupported features have been encountered, this
* {@link UnresolvedIdentifiersException} is thrown.
* </p>
*
* @param q The ADQL query to check.
*
* @throws ParseException If any unsupported language feature is used in
* the given ADQL tree.
*/
protected void generalChecks(final ADQLQuery q) throws ParseException {
// Create the exception in which errors have to be appended:
UnresolvedIdentifiersException exUnsupportedFeatures = new UnresolvedIdentifiersException("unsupported expression");
// Search recursively for all optional features inside the given tree:
SearchOptionalFeaturesHandler sFeaturesHandler = new SearchOptionalFeaturesHandler(true, false);
sFeaturesHandler.search(q);
// Append an error for each unsupported one:
for(ADQLObject obj : sFeaturesHandler) {
if (!supportedFeatures.isSupporting(obj.getFeatureDescription()))
exUnsupportedFeatures.addException(new UnsupportedFeatureException(obj));
}
// If unsupported features have been found, throw a ParseException:
if (exUnsupportedFeatures.getNbErrors() > 0)
throw exUnsupportedFeatures;
}
/** /**
* Parse the given <code>SELECT</code> clause. * Parse the given <code>SELECT</code> clause.
* *
...@@ -961,6 +1041,302 @@ public class ADQLParser { ...@@ -961,6 +1041,302 @@ public class ADQLParser {
} }
} }
/* **********************************************************************
* QUERY CHECKS *
********************************************************************** */
/**
* Run the general and common checks on the given ADQL tree.
*
* <p>
* By default, this function checks whether or not language features
* found in the given ADQL tree are supported. It also checks all explicit
* coordinate systems and STC-s expressions (embedded in REGION function).
* All unsupported expressions (i.e. feature, coord. sys., STC-s) are
* appended into an {@link UnresolvedIdentifiersException} which is finally
* thrown if not empty.
* </p>
*
* @param q The ADQL query to check.
*
* @throws ParseException If any unsupported language feature is used in
* the given ADQL tree.
*/
protected void generalChecks(final ADQLQuery q) throws ParseException {
// Create the exception in which errors have to be appended:
UnresolvedIdentifiersException exUnsupportedFeatures = new UnresolvedIdentifiersException("unsupported expression");
// Search recursively for all optional features inside the given tree:
SearchOptionalFeaturesHandler sFeaturesHandler = new SearchOptionalFeaturesHandler(true, false);
sFeaturesHandler.search(q);
// Append an error for each unsupported one:
for(ADQLObject obj : sFeaturesHandler) {
if (!supportedFeatures.isSupporting(obj.getFeatureDescription()))
exUnsupportedFeatures.addException(new UnsupportedFeatureException(obj));
}
// [only for ADQL-2.0] Resolve explicit coordinate system declarations:
resolveCoordinateSystems(q, exUnsupportedFeatures);
// [only for ADQL-2.0] Resolve explicit REGION declarations:
if (supportedFeatures.isSupporting(RegionFunction.FEATURE))
resolveSTCSExpressions(q, exUnsupportedFeatures);
// If unsupported features have been found, throw a ParseException:
if (exUnsupportedFeatures.getNbErrors() > 0)
throw exUnsupportedFeatures;
}
/**
* Transform the given collection of string elements into a sorted array.
* Only non-NULL and non-empty strings are kept.
*
* @param items Items to copy and sort.
*
* @return A sorted array containing all - except NULL and empty strings -
* items of the given collection.
*/
protected final String[] specialSort(final Collection<String> items) {
// Nothing to do if the array is NULL:
if (items == null)
return null;
// Keep only valid items (not NULL and not empty string):
String[] tmp = new String[items.size()];
int cnt = 0;
for(String item : items) {
if (item != null && item.trim().length() > 0)
tmp[cnt++] = item;
}
// Make an adjusted array copy:
String[] copy = new String[cnt];
System.arraycopy(tmp, 0, copy, 0, cnt);
// Sort the values:
Arrays.sort(copy);
return copy;
}
/**
* Search for all explicit coordinate system declarations, check their
* syntax and whether they are allowed by this implementation.
*
* <p><i><b>Note:</b>
* "explicit" means here that all {@link StringConstant} instances. Only
* coordinate systems expressed as string can be parsed and so checked. So
* if a coordinate system is specified by a column, no check can be done at
* this stage...it will be possible to perform such test only at the
* execution.
* </i></p>
*
* @param query Query in which coordinate systems must be checked.
* @param errors List of errors to complete in this function each time a
* coordinate system has a wrong syntax or is not
* supported.
*
* @see #checkCoordinateSystem(StringConstant, UnresolvedIdentifiersException)
*/
protected void resolveCoordinateSystems(final ADQLQuery query, final UnresolvedIdentifiersException errors) {
ISearchHandler sHandler = new SearchCoordSysHandler();
sHandler.search(query);
for(ADQLObject result : sHandler)
checkCoordinateSystem((StringConstant)result, errors);
}
/**
* Parse and then check the coordinate system contained in the given
* {@link StringConstant} instance.
*
* @param adqlCoordSys The {@link StringConstant} object containing the
* coordinate system to check.
* @param errors List of errors to complete in this function each
* time a coordinate system has a wrong syntax or is
* not supported.
*
* @see STCS#parseCoordSys(String)
* @see #checkCoordinateSystem(CoordSys, ADQLOperand, UnresolvedIdentifiersException)
*/
protected void checkCoordinateSystem(final StringConstant adqlCoordSys, final UnresolvedIdentifiersException errors) {
String coordSysStr = adqlCoordSys.getValue();
try {
checkCoordinateSystem(STCS.parseCoordSys(coordSysStr), adqlCoordSys, errors);
} catch(ParseException pe) {
errors.addException(new ParseException(pe.getMessage(), adqlCoordSys.getPosition()));
}
}
/**
* Check whether the given coordinate system is allowed by this
* implementation.
*
* @param coordSys Coordinate system to test.
* @param operand The operand representing or containing the coordinate
* system under test.
* @param errors List of errors to complete in this function each time a
* coordinate system is not supported.
*/
protected void checkCoordinateSystem(final CoordSys coordSys, final ADQLOperand operand, final UnresolvedIdentifiersException errors) {
if (coordSysRegExp != null && coordSys != null && !coordSys.toFullSTCS().matches(coordSysRegExp)) {
StringBuffer buf = new StringBuffer();
if (allowedCoordSys != null) {
for(String cs : allowedCoordSys) {
if (buf.length() > 0)
buf.append(", ");
buf.append(cs);
}
}
if (buf.length() == 0)
buf.append("No coordinate system is allowed!");
else
buf.insert(0, "Allowed coordinate systems are: ");
errors.addException(new ParseException("Coordinate system \"" + ((operand instanceof StringConstant) ? ((StringConstant)operand).getValue() : coordSys.toString()) + "\" (= \"" + coordSys.toFullSTCS() + "\") not allowed in this implementation. " + buf.toString(), operand.getPosition()));
}
}
/**
* Search for all STC-S expressions inside the given query, parse them (and
* so check their syntax) and then determine whether the declared coordinate
* system and the expressed region are allowed in this implementation.
*
* <p><i><b>Note:</b>
* In the current ADQL language definition, STC-S expressions can be found
* only as only parameter of the REGION function.
* </i></p>
*
* @param query Query in which STC-S expressions must be checked.
* @param errors List of errors to complete in this function each time
* the STC-S syntax is wrong or each time the declared
* coordinate system or region is not supported.
*
* @see STCS#parseRegion(String)
* @see #checkRegion(Region, RegionFunction, UnresolvedIdentifiersException)
*/
protected void resolveSTCSExpressions(final ADQLQuery query, final UnresolvedIdentifiersException errors) {
// Search REGION functions:
ISearchHandler sHandler = new SearchRegionHandler();
sHandler.search(query);
// Parse and check their STC-S expression:
String stcs;
Region region;
for(ADQLObject result : sHandler) {
try {
// get the STC-S expression:
stcs = ((StringConstant)((RegionFunction)result).getParameter(0)).getValue();
// parse the STC-S expression (and so check the syntax):
region = STCS.parseRegion(stcs);
// check whether the regions (this one + the possible inner ones) and the coordinate systems are allowed:
checkRegion(region, (RegionFunction)result, errors);
} catch(ParseException pe) {
errors.addException(new ParseException(pe.getMessage(), result.getPosition()));
}
}
}
/**
* Check the given region.
*
* <p>The following points are checked in this function:</p>
* <ul>
* <li>whether the coordinate system is allowed,</li>
* <li>whether the type of region is allowed,</li>
* <li>and whether the inner regions are correct (here this function is
* called recursively on each inner region).</li>
* </ul>
*
* @param r The region to check.
* @param fct The REGION function containing the region to check.
* @param errors List of errors to complete in this function if the given
* region or its inner regions are not supported.
*
* @see #checkCoordinateSystem(CoordSys, ADQLOperand, UnresolvedIdentifiersException)
* @see #checkRegion(Region, RegionFunction, UnresolvedIdentifiersException)
*/
protected void checkRegion(final Region r, final RegionFunction fct, final UnresolvedIdentifiersException errors) {
if (r == null)
return;
// Check the coordinate system (if any):
if (r.coordSys != null)
checkCoordinateSystem(r.coordSys, fct, errors);
// Check that the region type is allowed:
LanguageFeature feature;
switch(r.type) {
case POSITION:
feature = PointFunction.FEATURE;
break;
case BOX:
feature = BoxFunction.FEATURE;
break;
case CIRCLE:
feature = CircleFunction.FEATURE;
break;
case POLYGON:
feature = PolygonFunction.FEATURE;
break;
default:
/* TODO Add a case for UNION and INTERSECT when supported! */
feature = null;
break;
}
if (r.type != RegionType.NOT && (feature == null || !supportedFeatures.isSupporting(feature)))
errors.addException(new UnsupportedFeatureException(fct, "Unsupported STC-s region type: \"" + r.type + "\"" + (feature == null ? "!" : " (equivalent to the ADQL feature \"" + feature.form + "\" of type '" + feature.type + "')!")));
// Check all the inner regions:
if (r.regions != null) {
for(Region innerR : r.regions)
checkRegion(innerR, fct, errors);
}
}
/**
* Let searching all explicit declarations of coordinate system.
* So, only {@link StringConstant} objects will be returned.
*
* @author Gr&eacute;gory Mantelet (CDS)
* @version 2.0 (08/2019)
* @since 2.0
*/
private static class SearchCoordSysHandler extends SimpleSearchHandler {
@Override
protected boolean match(ADQLObject obj) {
if (obj instanceof PointFunction || obj instanceof BoxFunction || obj instanceof CircleFunction || obj instanceof PolygonFunction)
return (((GeometryFunction)obj).getCoordinateSystem() instanceof StringConstant);
else
return false;
}
@Override
protected void addMatch(ADQLObject matchObj, ADQLIterator it) {
results.add(((GeometryFunction)matchObj).getCoordinateSystem());
}
}
/**
* Let searching all {@link RegionFunction}s.
*
* @author Gr&eacute;gory Mantelet (CDS)
* @version 2.0 (08/2019)
* @since 2.0
*/
private static class SearchRegionHandler extends SimpleSearchHandler {
@Override
protected boolean match(ADQLObject obj) {
if (obj instanceof RegionFunction)
return (((RegionFunction)obj).getParameter(0) instanceof StringConstant);
else
return false;
}
}
/* ********************************************************************** /* **********************************************************************
* TOKENIZATION FUNCTION * * TOKENIZATION FUNCTION *
********************************************************************** */ ********************************************************************** */
......
...@@ -31,10 +31,6 @@ import adql.query.operand.ADQLOperand; ...@@ -31,10 +31,6 @@ import adql.query.operand.ADQLOperand;
import adql.query.operand.StringConstant; import adql.query.operand.StringConstant;
import adql.query.operand.function.DefaultUDF; import adql.query.operand.function.DefaultUDF;
import adql.query.operand.function.UserDefinedFunction; import adql.query.operand.function.UserDefinedFunction;
import adql.query.operand.function.geometry.CircleFunction;
import adql.query.operand.function.geometry.ContainsFunction;
import adql.query.operand.function.geometry.PointFunction;
import adql.query.operand.function.geometry.RegionFunction;
import adql.search.SimpleSearchHandler; import adql.search.SimpleSearchHandler;
import adql.translator.ADQLTranslator; import adql.translator.ADQLTranslator;
import adql.translator.PostgreSQLTranslator; import adql.translator.PostgreSQLTranslator;
...@@ -405,181 +401,6 @@ public class TestDBChecker { ...@@ -405,181 +401,6 @@ public class TestDBChecker {
} }
} }
@Test
public void testGeometry() {
// DECLARE A SIMPLE PARSER where all geometries are allowed by default:
ADQLParser parser = new ADQLParser();
parser.setQueryChecker(new DBChecker(tables));
// Test with several geometries while all are allowed:
try {
assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(POINT('', 12.3, 45.6), CIRCLE('', 1.2, 2.3, 5)) = 1;"));
} catch(ParseException pe) {
pe.printStackTrace();
fail("This query contains several geometries, and all are theoretically allowed: this test should have succeeded!");
}
// Test with several geometries while only the allowed ones:
try {
parser = new ADQLParser();
parser.getSupportedFeatures().unsupportAll(LanguageFeature.TYPE_ADQL_GEO);
parser.getSupportedFeatures().support(ContainsFunction.FEATURE);
parser.getSupportedFeatures().support(PointFunction.FEATURE);
parser.getSupportedFeatures().support(CircleFunction.FEATURE);
assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(POINT('', 12.3, 45.6), CIRCLE('', 1.2, 2.3, 5)) = 1;"));
} catch(ParseException pe) {
pe.printStackTrace();
fail("This query contains several geometries, and all are theoretically allowed: this test should have succeeded!");
}
try {
parser.parseQuery("SELECT * FROM foo WHERE INTERSECTS(POINT('', 12.3, 45.6), CIRCLE('', 1.2, 2.3, 5)) = 1;");
fail("This query contains a not-allowed geometry function (INTERSECTS): this test should have failed!");
} catch(ParseException pe) {
assertTrue(pe instanceof UnresolvedIdentifiersException);
UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)pe;
assertEquals(1, ex.getNbErrors());
assertEquals("Unsupported ADQL feature: \"INTERSECTS\" (of type 'ivo://ivoa.net/std/TAPRegExt#features-adql-geo')!", ex.getErrors().next().getMessage());
}
// Test by adding REGION:
try {
parser = new ADQLParser();
parser.getSupportedFeatures().unsupportAll(LanguageFeature.TYPE_ADQL_GEO);
parser.getSupportedFeatures().support(ContainsFunction.FEATURE);
parser.getSupportedFeatures().support(PointFunction.FEATURE);
parser.getSupportedFeatures().support(CircleFunction.FEATURE);
parser.getSupportedFeatures().support(RegionFunction.FEATURE);
assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(REGION('Position 12.3 45.6'), REGION('circle 1.2 2.3 5')) = 1;"));
} catch(ParseException pe) {
pe.printStackTrace();
fail("This query contains several geometries, and all are theoretically allowed: this test should have succeeded!");
}
// TODO Deal with un/supported Regions inside STC-S!
try {
parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(REGION('Position 12.3 45.6'), REGION('BOX 1.2 2.3 5 9')) = 1;");
fail("This query contains a not-allowed geometry function (BOX): this test should have failed!");
} catch(ParseException pe) {
assertTrue(pe instanceof UnresolvedIdentifiersException);
UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)pe;
assertEquals(1, ex.getNbErrors());
assertEquals("The geometrical function \"BOX\" is not available in this implementation!", ex.getErrors().next().getMessage());
}
// Test with several geometries while none geometry is allowed:
try {
parser = new ADQLParser();
parser.getSupportedFeatures().unsupportAll(LanguageFeature.TYPE_ADQL_GEO);
parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(POINT('', 12.3, 45.6), CIRCLE('', 1.2, 2.3, 5)) = 1;");
fail("This query contains geometries while they are all forbidden: this test should have failed!");
} catch(ParseException pe) {
assertTrue(pe instanceof UnresolvedIdentifiersException);
UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)pe;
assertEquals(3, ex.getNbErrors());
Iterator<ParseException> itErrors = ex.getErrors();
assertEquals("Unsupported ADQL feature: \"CONTAINS\" (of type 'ivo://ivoa.net/std/TAPRegExt#features-adql-geo')!", itErrors.next().getMessage());
assertEquals("Unsupported ADQL feature: \"POINT\" (of type 'ivo://ivoa.net/std/TAPRegExt#features-adql-geo')!", itErrors.next().getMessage());
assertEquals("Unsupported ADQL feature: \"CIRCLE\" (of type 'ivo://ivoa.net/std/TAPRegExt#features-adql-geo')!", itErrors.next().getMessage());
}
}
@Test
public void testCoordSys() {
// DECLARE A SIMPLE PARSER where all coordinate systems are allowed by default:
ADQLParser parser = new ADQLParser();
parser.setQueryChecker(new DBChecker(tables));
// Test with several coordinate systems while all are allowed:
try {
assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(POINT('', 12.3, 45.6), CIRCLE('', 1.2, 2.3, 5)) = 1;"));
assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(POINT('icrs', 12.3, 45.6), CIRCLE('cartesian2', 1.2, 2.3, 5)) = 1;"));
assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(POINT('lsr', 12.3, 45.6), CIRCLE('galactic heliocenter', 1.2, 2.3, 5)) = 1;"));
assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(POINT('unknownframe', 12.3, 45.6), CIRCLE('galactic unknownrefpos spherical2', 1.2, 2.3, 5)) = 1;"));
assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(REGION('position icrs lsr 12.3 45.6'), REGION('circle fk5 1.2 2.3 5')) = 1;"));
assertNotNull(parser.parseQuery("SELECT Region('not(position 1 2)') FROM foo;"));
} catch(ParseException pe) {
pe.printStackTrace();
fail("This query contains several valid coordinate systems, and all are theoretically allowed: this test should have succeeded!");
}
// Concatenation as coordinate systems not checked:
try {
assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(POINT('From ' || 'here', 12.3, 45.6), CIRCLE('', 1.2, 2.3, 5)) = 1;"));
} catch(ParseException pe) {
pe.printStackTrace();
fail("This query contains a concatenation as coordinate systems (but only string constants are checked): this test should have succeeded!");
}
// Test with several coordinate systems while only some allowed:
try {
parser = new ADQLParser();
parser.setQueryChecker(new DBChecker(tables, new ArrayList<FunctionDef>(0), null, Arrays.asList(new String[]{ "icrs * *", "fk4 geocenter *", "galactic * spherical2" })));
assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(POINT('', 12.3, 45.6), CIRCLE('', 1.2, 2.3, 5)) = 1;"));
assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(POINT('icrs', 12.3, 45.6), CIRCLE('cartesian3', 1.2, 2.3, 5)) = 1;"));
assertNotNull(parser.parseQuery("SELECT POINT('fk4', 12.3, 45.6) FROM foo;"));
assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(POINT('fk4 geocenter', 12.3, 45.6), CIRCLE('cartesian2', 1.2, 2.3, 5)) = 1;"));
assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(POINT('galactic', 12.3, 45.6), CIRCLE('galactic spherical2', 1.2, 2.3, 5)) = 1;"));
assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(POINT('galactic geocenter', 12.3, 45.6), CIRCLE('galactic lsr spherical2', 1.2, 2.3, 5)) = 1;"));
assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(REGION('position galactic lsr 12.3 45.6'), REGION('circle icrs 1.2 2.3 5')) = 1;"));
assertNotNull(parser.parseQuery("SELECT Region('not(position 1 2)') FROM foo;"));
} catch(ParseException pe) {
pe.printStackTrace();
fail("This query contains several valid coordinate systems, and all are theoretically allowed: this test should have succeeded!");
}
try {
parser.parseQuery("SELECT POINT('fk5 geocenter', 12.3, 45.6) FROM foo;");
fail("This query contains a not-allowed coordinate system ('fk5' is not allowed): this test should have failed!");
} catch(ParseException pe) {
assertTrue(pe instanceof UnresolvedIdentifiersException);
UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)pe;
assertEquals(1, ex.getNbErrors());
assertEquals("Coordinate system \"fk5 geocenter\" (= \"FK5 GEOCENTER SPHERICAL2\") not allowed in this implementation. Allowed coordinate systems are: fk4 geocenter *, galactic * spherical2, icrs * *", ex.getErrors().next().getMessage());
}
try {
parser.parseQuery("SELECT Region('not(position fk5 heliocenter 1 2)') FROM foo;");
fail("This query contains a not-allowed coordinate system ('fk5' is not allowed): this test should have failed!");
} catch(ParseException pe) {
assertTrue(pe instanceof UnresolvedIdentifiersException);
UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)pe;
assertEquals(1, ex.getNbErrors());
assertEquals("Coordinate system \"FK5 HELIOCENTER\" (= \"FK5 HELIOCENTER SPHERICAL2\") not allowed in this implementation. Allowed coordinate systems are: fk4 geocenter *, galactic * spherical2, icrs * *", ex.getErrors().next().getMessage());
}
// Test with a coordinate system while none is allowed:
try {
parser = new ADQLParser();
parser.setQueryChecker(new DBChecker(tables, new ArrayList<FunctionDef>(0), null, new ArrayList<String>(0)));
assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(POINT('', 12.3, 45.6), CIRCLE('', 1.2, 2.3, 5)) = 1;"));
assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(REGION('position 12.3 45.6'), REGION('circle 1.2 2.3 5')) = 1;"));
assertNotNull(parser.parseQuery("SELECT Region('not(position 1 2)') FROM foo;"));
} catch(ParseException pe) {
pe.printStackTrace();
fail("This query specifies none coordinate system: this test should have succeeded!");
}
try {
parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(POINT('ICRS SPHERICAL2', 12.3, 45.6), CIRCLE('icrs', 1.2, 2.3, 5)) = 1;");
fail("This query specifies coordinate systems while they are all forbidden: this test should have failed!");
} catch(ParseException pe) {
assertTrue(pe instanceof UnresolvedIdentifiersException);
UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)pe;
assertEquals(2, ex.getNbErrors());
Iterator<ParseException> itErrors = ex.getErrors();
assertEquals("Coordinate system \"ICRS SPHERICAL2\" (= \"ICRS UNKNOWNREFPOS SPHERICAL2\") not allowed in this implementation. No coordinate system is allowed!", itErrors.next().getMessage());
assertEquals("Coordinate system \"icrs\" (= \"ICRS UNKNOWNREFPOS SPHERICAL2\") not allowed in this implementation. No coordinate system is allowed!", itErrors.next().getMessage());
}
try {
parser.parseQuery("SELECT Region('not(position fk4 1 2)') FROM foo;");
fail("This query specifies coordinate systems while they are all forbidden: this test should have failed!");
} catch(ParseException pe) {
assertTrue(pe instanceof UnresolvedIdentifiersException);
UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)pe;
assertEquals(1, ex.getNbErrors());
assertEquals("Coordinate system \"FK4\" (= \"FK4 UNKNOWNREFPOS SPHERICAL2\") not allowed in this implementation. No coordinate system is allowed!", ex.getErrors().next().getMessage());
}
}
@Test @Test
public void testTypesChecking() { public void testTypesChecking() {
// DECLARE A SIMPLE PARSER: // DECLARE A SIMPLE PARSER:
......
...@@ -5,12 +5,17 @@ import static org.junit.Assert.assertNotNull; ...@@ -5,12 +5,17 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import org.junit.After; import org.junit.After;
import org.junit.AfterClass; import org.junit.AfterClass;
import org.junit.Before; import org.junit.Before;
import org.junit.BeforeClass; import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
import adql.db.FunctionDef;
import adql.db.exception.UnresolvedIdentifiersException; import adql.db.exception.UnresolvedIdentifiersException;
import adql.db.exception.UnsupportedFeatureException; import adql.db.exception.UnsupportedFeatureException;
import adql.parser.ADQLParser.ADQLVersion; import adql.parser.ADQLParser.ADQLVersion;
...@@ -22,7 +27,10 @@ import adql.query.ADQLQuery; ...@@ -22,7 +27,10 @@ import adql.query.ADQLQuery;
import adql.query.from.ADQLJoin; import adql.query.from.ADQLJoin;
import adql.query.from.ADQLTable; import adql.query.from.ADQLTable;
import adql.query.operand.StringConstant; import adql.query.operand.StringConstant;
import adql.query.operand.function.geometry.CircleFunction;
import adql.query.operand.function.geometry.ContainsFunction;
import adql.query.operand.function.geometry.PointFunction; import adql.query.operand.function.geometry.PointFunction;
import adql.query.operand.function.geometry.RegionFunction;
import adql.query.operand.function.string.LowerFunction; import adql.query.operand.function.string.LowerFunction;
public class TestADQLParser { public class TestADQLParser {
...@@ -331,7 +339,6 @@ public class TestADQLParser { ...@@ -331,7 +339,6 @@ public class TestADQLParser {
@Test @Test
public void testUDFName() { public void testUDFName() {
ADQLParser parser = new ADQLParser(); ADQLParser parser = new ADQLParser();
// TODO [ADQL-2.1] Add the support for this specific UDF in the the FeatureSet!
// CASE: Valid UDF name => OK // CASE: Valid UDF name => OK
try { try {
...@@ -354,6 +361,39 @@ public class TestADQLParser { ...@@ -354,6 +361,39 @@ public class TestADQLParser {
} }
} }
@Test
public void testUDFDeclaration() {
ADQLParser parser = new ADQLParser();
// CASE: Any UDF allowed => OK!
parser.getSupportedFeatures().allowAnyUdf(true);
try {
assertNotNull(parser.parseQuery("SELECT foo(1,2) FROM bar"));
} catch(Throwable t) {
t.printStackTrace();
fail("Unexpected parsing error! This query should have passed. (see console for more details)");
}
// CASE: No UDF allowed => ERROR
parser.getSupportedFeatures().allowAnyUdf(false);
try {
parser.parseQuery("SELECT foo(1,2) FROM bar");
fail("No UDF is allowed. This query should have failed!");
} catch(Throwable t) {
assertEquals(UnresolvedIdentifiersException.class, t.getClass());
assertEquals("1 unsupported expressions!\n - Unsupported ADQL feature: \"foo(param1 ?type?, param2 ?type?) -> ?type?\" (of type 'ivo://ivoa.net/std/TAPRegExt#features-udf')!", t.getMessage());
}
// CASE: a single UDF declared => OK!
try {
parser.getSupportedFeatures().support(FunctionDef.parse("foo(i1 INTEGER, i2 INTEGER) -> INTEGER").toLanguageFeature());
assertNotNull(parser.parseQuery("SELECT foo(1,2) FROM bar"));
} catch(Throwable t) {
t.printStackTrace();
fail("Unexpected parsing error! This query should have passed. (see console for more details)");
}
}
@Test @Test
public void testOptionalFeatures() { public void testOptionalFeatures() {
ADQLParser parser = new ADQLParser(ADQLVersion.V2_0); ADQLParser parser = new ADQLParser(ADQLVersion.V2_0);
...@@ -432,6 +472,178 @@ public class TestADQLParser { ...@@ -432,6 +472,178 @@ public class TestADQLParser {
} }
} }
@Test
public void testGeometry() {
// DECLARE A SIMPLE PARSER where all geometries are allowed by default:
ADQLParser parser = new ADQLParser();
// Test with several geometries while all are allowed:
try {
assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(POINT('', 12.3, 45.6), CIRCLE('', 1.2, 2.3, 5)) = 1;"));
} catch(ParseException pe) {
pe.printStackTrace();
fail("This query contains several geometries, and all are theoretically allowed: this test should have succeeded!");
}
// Test with several geometries while only the allowed ones:
try {
parser = new ADQLParser();
parser.getSupportedFeatures().unsupportAll(LanguageFeature.TYPE_ADQL_GEO);
parser.getSupportedFeatures().support(ContainsFunction.FEATURE);
parser.getSupportedFeatures().support(PointFunction.FEATURE);
parser.getSupportedFeatures().support(CircleFunction.FEATURE);
assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(POINT('', 12.3, 45.6), CIRCLE('', 1.2, 2.3, 5)) = 1;"));
} catch(ParseException pe) {
pe.printStackTrace();
fail("This query contains several geometries, and all are theoretically allowed: this test should have succeeded!");
}
try {
parser.parseQuery("SELECT * FROM foo WHERE INTERSECTS(POINT('', 12.3, 45.6), CIRCLE('', 1.2, 2.3, 5)) = 1;");
fail("This query contains a not-allowed geometry function (INTERSECTS): this test should have failed!");
} catch(ParseException pe) {
assertTrue(pe instanceof UnresolvedIdentifiersException);
UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)pe;
assertEquals(1, ex.getNbErrors());
assertEquals("Unsupported ADQL feature: \"INTERSECTS\" (of type 'ivo://ivoa.net/std/TAPRegExt#features-adql-geo')!", ex.getErrors().next().getMessage());
}
// Test by adding REGION:
try {
parser = new ADQLParser();
parser.getSupportedFeatures().unsupportAll(LanguageFeature.TYPE_ADQL_GEO);
parser.getSupportedFeatures().support(ContainsFunction.FEATURE);
parser.getSupportedFeatures().support(PointFunction.FEATURE);
parser.getSupportedFeatures().support(CircleFunction.FEATURE);
parser.getSupportedFeatures().support(RegionFunction.FEATURE);
assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(REGION('Position 12.3 45.6'), REGION('circle 1.2 2.3 5')) = 1;"));
} catch(ParseException pe) {
pe.printStackTrace();
fail("This query contains several geometries, and all are theoretically allowed: this test should have succeeded!");
}
try {
parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(REGION('Position 12.3 45.6'), REGION('BOX 1.2 2.3 5 9')) = 1;");
fail("This query contains a not-allowed geometry function (BOX): this test should have failed!");
} catch(ParseException pe) {
assertTrue(pe instanceof UnresolvedIdentifiersException);
UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)pe;
assertEquals(1, ex.getNbErrors());
assertEquals("Unsupported STC-s region type: \"BOX\" (equivalent to the ADQL feature \"BOX\" of type 'ivo://ivoa.net/std/TAPRegExt#features-adql-geo')!", ex.getErrors().next().getMessage());
}
// Test with several geometries while none geometry is allowed:
try {
parser = new ADQLParser();
parser.getSupportedFeatures().unsupportAll(LanguageFeature.TYPE_ADQL_GEO);
parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(POINT('', 12.3, 45.6), CIRCLE('', 1.2, 2.3, 5)) = 1;");
fail("This query contains geometries while they are all forbidden: this test should have failed!");
} catch(ParseException pe) {
assertTrue(pe instanceof UnresolvedIdentifiersException);
UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)pe;
assertEquals(3, ex.getNbErrors());
Iterator<ParseException> itErrors = ex.getErrors();
assertEquals("Unsupported ADQL feature: \"CONTAINS\" (of type 'ivo://ivoa.net/std/TAPRegExt#features-adql-geo')!", itErrors.next().getMessage());
assertEquals("Unsupported ADQL feature: \"POINT\" (of type 'ivo://ivoa.net/std/TAPRegExt#features-adql-geo')!", itErrors.next().getMessage());
assertEquals("Unsupported ADQL feature: \"CIRCLE\" (of type 'ivo://ivoa.net/std/TAPRegExt#features-adql-geo')!", itErrors.next().getMessage());
}
}
@Test
public void testCoordSys() {
// DECLARE A SIMPLE PARSER where all coordinate systems are allowed by default:
ADQLParser parser = new ADQLParser();
// Test with several coordinate systems while all are allowed:
try {
assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(POINT('', 12.3, 45.6), CIRCLE('', 1.2, 2.3, 5)) = 1;"));
assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(POINT('icrs', 12.3, 45.6), CIRCLE('cartesian2', 1.2, 2.3, 5)) = 1;"));
assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(POINT('lsr', 12.3, 45.6), CIRCLE('galactic heliocenter', 1.2, 2.3, 5)) = 1;"));
assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(POINT('unknownframe', 12.3, 45.6), CIRCLE('galactic unknownrefpos spherical2', 1.2, 2.3, 5)) = 1;"));
assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(REGION('position icrs lsr 12.3 45.6'), REGION('circle fk5 1.2 2.3 5')) = 1;"));
assertNotNull(parser.parseQuery("SELECT Region('not(position 1 2)') FROM foo;"));
} catch(ParseException pe) {
pe.printStackTrace();
fail("This query contains several valid coordinate systems, and all are theoretically allowed: this test should have succeeded!");
}
// Concatenation as coordinate systems not checked:
try {
assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(POINT('From ' || 'here', 12.3, 45.6), CIRCLE('', 1.2, 2.3, 5)) = 1;"));
} catch(ParseException pe) {
pe.printStackTrace();
fail("This query contains a concatenation as coordinate systems (but only string constants are checked): this test should have succeeded!");
}
// Test with several coordinate systems while only some allowed:
try {
parser = new ADQLParser();
parser.setAllowedCoordSys(Arrays.asList(new String[]{ "icrs * *", "fk4 geocenter *", "galactic * spherical2" }));
assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(POINT('', 12.3, 45.6), CIRCLE('', 1.2, 2.3, 5)) = 1;"));
assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(POINT('icrs', 12.3, 45.6), CIRCLE('cartesian3', 1.2, 2.3, 5)) = 1;"));
assertNotNull(parser.parseQuery("SELECT POINT('fk4', 12.3, 45.6) FROM foo;"));
assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(POINT('fk4 geocenter', 12.3, 45.6), CIRCLE('cartesian2', 1.2, 2.3, 5)) = 1;"));
assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(POINT('galactic', 12.3, 45.6), CIRCLE('galactic spherical2', 1.2, 2.3, 5)) = 1;"));
assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(POINT('galactic geocenter', 12.3, 45.6), CIRCLE('galactic lsr spherical2', 1.2, 2.3, 5)) = 1;"));
assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(REGION('position galactic lsr 12.3 45.6'), REGION('circle icrs 1.2 2.3 5')) = 1;"));
assertNotNull(parser.parseQuery("SELECT Region('not(position 1 2)') FROM foo;"));
} catch(ParseException pe) {
pe.printStackTrace();
fail("This query contains several valid coordinate systems, and all are theoretically allowed: this test should have succeeded!");
}
try {
parser.parseQuery("SELECT POINT('fk5 geocenter', 12.3, 45.6) FROM foo;");
fail("This query contains a not-allowed coordinate system ('fk5' is not allowed): this test should have failed!");
} catch(ParseException pe) {
assertTrue(pe instanceof UnresolvedIdentifiersException);
UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)pe;
assertEquals(1, ex.getNbErrors());
assertEquals("Coordinate system \"fk5 geocenter\" (= \"FK5 GEOCENTER SPHERICAL2\") not allowed in this implementation. Allowed coordinate systems are: fk4 geocenter *, galactic * spherical2, icrs * *", ex.getErrors().next().getMessage());
}
try {
parser.parseQuery("SELECT Region('not(position fk5 heliocenter 1 2)') FROM foo;");
fail("This query contains a not-allowed coordinate system ('fk5' is not allowed): this test should have failed!");
} catch(ParseException pe) {
assertTrue(pe instanceof UnresolvedIdentifiersException);
UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)pe;
assertEquals(1, ex.getNbErrors());
assertEquals("Coordinate system \"FK5 HELIOCENTER\" (= \"FK5 HELIOCENTER SPHERICAL2\") not allowed in this implementation. Allowed coordinate systems are: fk4 geocenter *, galactic * spherical2, icrs * *", ex.getErrors().next().getMessage());
}
// Test with a coordinate system while none is allowed:
try {
parser = new ADQLParser();
parser.setAllowedCoordSys(new ArrayList<String>(0));
assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(POINT('', 12.3, 45.6), CIRCLE('', 1.2, 2.3, 5)) = 1;"));
assertNotNull(parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(REGION('position 12.3 45.6'), REGION('circle 1.2 2.3 5')) = 1;"));
assertNotNull(parser.parseQuery("SELECT Region('not(position 1 2)') FROM foo;"));
} catch(ParseException pe) {
pe.printStackTrace();
fail("This query specifies none coordinate system: this test should have succeeded!");
}
try {
parser.parseQuery("SELECT * FROM foo WHERE CONTAINS(POINT('ICRS SPHERICAL2', 12.3, 45.6), CIRCLE('icrs', 1.2, 2.3, 5)) = 1;");
fail("This query specifies coordinate systems while they are all forbidden: this test should have failed!");
} catch(ParseException pe) {
assertTrue(pe instanceof UnresolvedIdentifiersException);
UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)pe;
assertEquals(2, ex.getNbErrors());
Iterator<ParseException> itErrors = ex.getErrors();
assertEquals("Coordinate system \"ICRS SPHERICAL2\" (= \"ICRS UNKNOWNREFPOS SPHERICAL2\") not allowed in this implementation. No coordinate system is allowed!", itErrors.next().getMessage());
assertEquals("Coordinate system \"icrs\" (= \"ICRS UNKNOWNREFPOS SPHERICAL2\") not allowed in this implementation. No coordinate system is allowed!", itErrors.next().getMessage());
}
try {
parser.parseQuery("SELECT Region('not(position fk4 1 2)') FROM foo;");
fail("This query specifies coordinate systems while they are all forbidden: this test should have failed!");
} catch(ParseException pe) {
assertTrue(pe instanceof UnresolvedIdentifiersException);
UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)pe;
assertEquals(1, ex.getNbErrors());
assertEquals("Coordinate system \"FK4\" (= \"FK4 UNKNOWNREFPOS SPHERICAL2\") not allowed in this implementation. No coordinate system is allowed!", ex.getErrors().next().getMessage());
}
}
@Test @Test
public void testTokenize() { public void testTokenize() {
ADQLParser parser = new ADQLParser(ADQLVersion.V2_0); ADQLParser parser = new ADQLParser(ADQLVersion.V2_0);
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment