Commit fc38da51 authored by gmantele's avatar gmantele
Browse files

ADQL: Fix an ADQL bug (raised by Dave Morris) in the management of subqueries:...

ADQL: Fix an ADQL bug (raised by Dave Morris) in the management of subqueries: before, it was impossible to use (in a clause different from the FROM) columns of a father query inside a subquery.
parent 510217ad
Loading
Loading
Loading
Loading
+119 −14
Original line number Diff line number Diff line
@@ -23,12 +23,15 @@ package adql.db;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Stack;

import adql.db.exception.UnresolvedColumnException;
import adql.db.exception.UnresolvedIdentifiersException;
import adql.db.exception.UnresolvedTableException;
import adql.parser.ParseException;
import adql.parser.QueryChecker;
import adql.query.ADQLIterator;
import adql.query.ADQLObject;
import adql.query.ADQLQuery;
import adql.query.ClauseSelect;
@@ -37,6 +40,7 @@ import adql.query.IdentifierField;
import adql.query.SelectAllColumns;
import adql.query.SelectItem;
import adql.query.from.ADQLTable;
import adql.query.from.FromContent;
import adql.query.operand.ADQLColumn;
import adql.search.ISearchHandler;
import adql.search.SearchColumnHandler;
@@ -60,7 +64,7 @@ import adql.search.SimpleSearchHandler;
 * </i></p>
 * 
 * @author Gr&eacute;gory Mantelet (CDS;ARI)
 * @version 1.1 (11/2013)
 * @version 1.2 (04/2014)
 */
public class DBChecker implements QueryChecker {

@@ -111,6 +115,26 @@ public class DBChecker implements QueryChecker {
	/* ************* */
	/* CHECK METHODS */
	/* ************* */
	/**
	 * <p>Check all the columns, tables and UDFs references inside the given query.</p>
	 * 
	 * <p><i>
	 * 	<u>Note:</u> This query has already been parsed ; thus it is already syntactically correct.
	 * 	Only the consistency with the published tables, columns and all the defined UDFs must be checked.
	 * </i></p>
	 * 
	 * @param query			The query to check.
	 * @param fatherColumns	List of all columns available in the father query.
	 * 
	 * @throws ParseException	An {@link UnresolvedIdentifiersException} if some tables or columns can not be resolved.
	 * 
	 * @see #check(ADQLQuery, Stack)
	 */
	@Override
	public void check(final ADQLQuery query) throws ParseException{
		check(query, null);
	}

	/**
	 * Followed algorithm:
	 * <pre>
@@ -151,16 +175,18 @@ public class DBChecker implements QueryChecker {
	 * </pre>
	 * 
	 * @param query			The query to check.
	 * @param fathersList	List of all columns available in the father query.
	 * 
	 * @throws ParseException	An {@link UnresolvedIdentifiersException} if some tables or columns can not be resolved.
	 * @throws UnresolvedIdentifiersException	An {@link UnresolvedIdentifiersException} if some tables or columns can not be resolved.
	 * 
	 * @since 1.2
	 * 
	 * @see #resolveTable(ADQLTable)
	 * @see #generateDBTable(ADQLQuery, String)
	 * @see #resolveColumn(ADQLColumn, SearchColumnList)
	 * @see #checkColumnReference(ColumnReference, ClauseSelect, SearchColumnList)
	 */
	@Override
	public void check(final ADQLQuery query) throws ParseException{
	protected void check(final ADQLQuery query, Stack<SearchColumnList> fathersList) throws UnresolvedIdentifiersException{
		UnresolvedIdentifiersException errors = new UnresolvedIdentifiersException();
		HashMap<DBTable,ADQLTable> mapTables = new HashMap<DBTable,ADQLTable>();
		ISearchHandler sHandler;
@@ -171,15 +197,20 @@ public class DBChecker implements QueryChecker {
		for(ADQLObject result : sHandler){
			try{
				ADQLTable table = (ADQLTable)result;

				// resolve the table:
				DBTable dbTable = null;
				if (table.isSubQuery()){
					// check the subquery tables:
					check(table.getSubQuery(), fathersList);
					// generate its DBTable:
					dbTable = generateDBTable(table.getSubQuery(), table.getAlias());
				}else{
					dbTable = resolveTable(table);
					if (table.hasAlias())
						dbTable = dbTable.copy(dbTable.getDBName(), table.getAlias());
				}

				// link with the matched DBTable:
				table.setDBLink(dbTable);
				mapTables.put(dbTable, table);
@@ -189,6 +220,10 @@ public class DBChecker implements QueryChecker {
		}

		// Attach table information on wildcards with the syntax "{tableName}.*" of the SELECT clause:
		/* Note: no need to check the table name among the father tables, because there is
		 *       no interest to select a father column in a subquery
		 *       (which can return only one column ; besides, no aggregate is not allowed
		 *       in subqueries).*/
		sHandler = new SearchWildCardHandler();
		sHandler.search(query.getSelect());
		for(ADQLObject result : sHandler){
@@ -213,6 +248,7 @@ public class DBChecker implements QueryChecker {
			}
		}

		// Get the list of all columns made available in the clause FROM:
		SearchColumnList list;
		try{
			list = query.getFrom().getDBColumns();
@@ -228,7 +264,7 @@ public class DBChecker implements QueryChecker {
			try{
				ADQLColumn adqlColumn = (ADQLColumn)result;
				// resolve the column:
				DBColumn dbColumn = resolveColumn(adqlColumn, list);
				DBColumn dbColumn = resolveColumn(adqlColumn, list, fathersList);
				// link with the matched DBColumn:
				adqlColumn.setDBLink(dbColumn);
				adqlColumn.setAdqlTable(mapTables.get(dbColumn.getTable()));
@@ -238,6 +274,8 @@ public class DBChecker implements QueryChecker {
		}

		// Check the correctness of all column references:
		/* Note: no need to provide the father tables when resolving column references,
		 *       because no father column can be used in ORDER BY and/or GROUP BY. */
		sHandler = new SearchColReferenceHandler();
		sHandler.search(query);
		ClauseSelect select = query.getSelect();
@@ -255,6 +293,32 @@ public class DBChecker implements QueryChecker {
			}
		}

		// Check subqueries outside the clause FROM:
		sHandler = new SearchSubQueryHandler();
		sHandler.search(query);
		if (sHandler.getNbMatch() > 0){

			// Push the list of columns in the father columns stack:
			if (fathersList == null)
				fathersList = new Stack<SearchColumnList>();
			fathersList.push(list);

			// Check each found subquery (except the first one because it is the current query):
			for(ADQLObject result : sHandler){
				try{
					check((ADQLQuery)result, fathersList);
				}catch(UnresolvedIdentifiersException uie){
					Iterator<ParseException> itPe = uie.getErrors();
					while(itPe.hasNext())
						errors.addException(itPe.next());
				}
			}

			// Pop the list of columns from the father columns stack:
			fathersList.pop();

		}

		// Throw all errors if any:
		if (errors.getNbErrors() > 0)
			throw errors;
@@ -284,17 +348,21 @@ public class DBChecker implements QueryChecker {
	}

	/**
	 * Resolves the given column, that's to say searches for the corresponding {@link DBColumn}.
	 * <p>Resolves the given column, that's to say searches for the corresponding {@link DBColumn}.</p>
	 * <p>The third parameter is used only if this function is called inside a subquery. In this case,
	 * column is tried to be resolved with the first list (dbColumns). If no match is found,
	 * the resolution is tried with the father columns list (fatherColumns).</p>
	 * 
	 * @param column		The column to resolve.
	 * @param dbColumns		List of all available {@link DBColumn}s.
	 * @param fathersList	List of all columns available in the father query ; a list for each father-level.
	 * 
	 * @return				The corresponding {@link DBColumn} if found, <i>null</i> otherwise.
	 * @return 				The corresponding {@link DBColumn} if found. Otherwise an exception is thrown.
	 * 
	 * @throws ParseException	An {@link UnresolvedColumnException} if the given column can't be resolved
	 * 							or an {@link UnresolvedTableException} if its table reference can't be resolved.
	 */
	protected DBColumn resolveColumn(final ADQLColumn column, final SearchColumnList dbColumns) throws ParseException{
	protected DBColumn resolveColumn(final ADQLColumn column, final SearchColumnList dbColumns, Stack<SearchColumnList> fathersList) throws ParseException{
		ArrayList<DBColumn> foundColumns = dbColumns.search(column);

		// good if only one column has been found:
@@ -307,8 +375,15 @@ public class DBChecker implements QueryChecker {
			else
				throw new UnresolvedTableException(column, (foundColumns.get(0).getTable() == null) ? "<NULL>" : foundColumns.get(0).getTable().getADQLName(), (foundColumns.get(1).getTable() == null) ? "<NULL>" : foundColumns.get(1).getTable().getADQLName());
		}// otherwise (no match): unknown column !
		else
		else{
			if (fathersList == null || fathersList.isEmpty())
				throw new UnresolvedColumnException(column);
			else{
				Stack<SearchColumnList> subStack = new Stack<SearchColumnList>();
				subStack.addAll(fathersList.subList(0, fathersList.size() - 1));
				return resolveColumn(column, fathersList.peek(), subStack);
			}
		}
	}

	/**
@@ -325,7 +400,7 @@ public class DBChecker implements QueryChecker {
	 * 							or an {@link UnresolvedTableException} if its table reference can't be resolved.
	 * 
	 * @see ClauseSelect#searchByAlias(String)
	 * @see #resolveColumn(ADQLColumn, SearchColumnList)
	 * @see #resolveColumn(ADQLColumn, SearchColumnList, SearchColumnList)
	 */
	protected DBColumn checkColumnReference(final ColumnReference colRef, final ClauseSelect select, final SearchColumnList dbColumns) throws ParseException{
		if (colRef.isIndex()){
@@ -352,7 +427,7 @@ public class DBChecker implements QueryChecker {
			}

			// check the corresponding column:
			return resolveColumn(col, dbColumns);
			return resolveColumn(col, dbColumns, null);
		}
	}

@@ -422,4 +497,34 @@ public class DBChecker implements QueryChecker {
		}
	}

	/**
	 * <p>Lets searching subqueries in every clause except the FROM one (hence the modification of the {@link #goInto(ADQLObject)}.</p>
	 * 
	 * <p><i>
	 * 	<u>Note:</u> The function {@link #addMatch(ADQLObject, ADQLIterator)} has been modified in order to
	 * 	not have the root search object (here: the main query) in the list of results.
	 * </i></p>
	 * 
	 * @author Gr&eacute;gory Mantelet (ARI)
	 * @version 1.2 (12/2013)
	 * @since 1.2
	 */
	private static class SearchSubQueryHandler extends SimpleSearchHandler {
		@Override
		protected void addMatch(ADQLObject matchObj, ADQLIterator it){
			if (it != null)
				super.addMatch(matchObj, it);
		}

		@Override
		protected boolean goInto(ADQLObject obj){
			return super.goInto(obj) && !(obj instanceof FromContent);
		}

		@Override
		protected boolean match(ADQLObject obj){
			return (obj instanceof ADQLQuery);
		}
	}

}
+244 −229
Original line number Diff line number Diff line
/* Generated By:JavaCC: Do not edit this line. ADQLParser.java */
package adql.parser;

import java.util.Stack;
import java.util.Vector;
import java.util.ArrayList;
import java.util.Collection;

import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Stack;
import java.util.Vector;

import adql.db.exception.UnresolvedIdentifiersException;

import adql.parser.IdentifierItems.IdentifierItem;

import adql.parser.ADQLQueryFactory.JoinType;

import adql.query.*;
import adql.query.from.*;
import adql.query.constraint.*;

import adql.query.operand.*;

import adql.query.operand.function.*;

import adql.query.operand.function.geometry.*;
import adql.parser.IdentifierItems.IdentifierItem;
import adql.query.ADQLOrder;
import adql.query.ADQLQuery;
import adql.query.ClauseADQL;
import adql.query.ClauseConstraints;
import adql.query.ClauseSelect;
import adql.query.ColumnReference;
import adql.query.SelectAllColumns;
import adql.query.SelectItem;
import adql.query.TextPosition;
import adql.query.constraint.ADQLConstraint;
import adql.query.constraint.Between;
import adql.query.constraint.Comparison;
import adql.query.constraint.ComparisonOperator;
import adql.query.constraint.ConstraintsGroup;
import adql.query.constraint.In;
import adql.query.from.ADQLJoin;
import adql.query.from.FromContent;
import adql.query.operand.ADQLColumn;
import adql.query.operand.ADQLOperand;
import adql.query.operand.Concatenation;
import adql.query.operand.OperationType;
import adql.query.operand.StringConstant;
import adql.query.operand.function.ADQLFunction;
import adql.query.operand.function.MathFunction;
import adql.query.operand.function.MathFunctionType;
import adql.query.operand.function.SQLFunction;
import adql.query.operand.function.SQLFunctionType;
import adql.query.operand.function.UserDefinedFunction;
import adql.query.operand.function.geometry.GeometryFunction;
import adql.query.operand.function.geometry.GeometryFunction.GeometryValue;

import adql.query.operand.function.geometry.PointFunction;
import adql.translator.PostgreSQLTranslator;
import adql.translator.TranslationException;

@@ -44,10 +60,9 @@ import adql.translator.TranslationException;
* @see QueryChecker
* @see ADQLQueryFactory
*
 * @author Gr&eacute;gory Mantelet (CDS) - gregory.mantelet@astro.unistra.fr
 * @version January 2012
* @author Gr&eacute;gory Mantelet (CDS;ARI) - gmantele@ari.uni-heidelberg.de
* @version 1.2 (12/2013)
*/
@SuppressWarnings("all")
public class ADQLParser implements ADQLParserConstants {

	/** Tools to build the object representation of the ADQL query. */
@@ -494,6 +509,10 @@ public class ADQLParser implements ADQLParserConstants {
					jj_consume_token(-1);
					throw new ParseException();
			}
			// check the query:
			if (queryChecker != null)
				queryChecker.check(q);

			{
				if (true)
					return q;
@@ -551,10 +570,6 @@ public class ADQLParser implements ADQLParserConstants {
					jj_la1[4] = jj_gen;
					;
			}
			// check the query:
			if (queryChecker != null)
				queryChecker.check(query);

			// get the previous query (!= null if the current query is a sub-query):
			ADQLQuery previousQuery = stackQuery.pop();
			if (stackQuery.isEmpty())
@@ -3549,34 +3564,6 @@ public class ADQLParser implements ADQLParserConstants {
		}
	}

	private boolean jj_3R_165(){
		if (jj_scan_token(NOT))
			return true;
		return false;
	}

	private boolean jj_3R_44(){
		if (jj_scan_token(SELECT))
			return true;
		Token xsp;
		xsp = jj_scanpos;
		if (jj_3R_136())
			jj_scanpos = xsp;
		xsp = jj_scanpos;
		if (jj_3R_137())
			jj_scanpos = xsp;
		if (jj_3R_138())
			return true;
		while(true){
			xsp = jj_scanpos;
			if (jj_3R_139()){
				jj_scanpos = xsp;
				break;
			}
		}
		return false;
	}

	private boolean jj_3R_16(){
		if (jj_scan_token(LEFT_PAR))
			return true;
@@ -3616,44 +3603,44 @@ public class ADQLParser implements ADQLParserConstants {
		return false;
	}

	private boolean jj_3R_111(){
		if (jj_3R_23())
	private boolean jj_3R_120(){
		if (jj_3R_143())
			return true;
		return false;
	}

	private boolean jj_3R_163(){
		if (jj_3R_49())
	private boolean jj_3R_119(){
		if (jj_3R_142())
			return true;
		return false;
	}

	private boolean jj_3R_120(){
		if (jj_3R_143())
	private boolean jj_3R_118(){
		if (jj_3R_141())
			return true;
		return false;
	}

	private boolean jj_3R_64(){
		Token xsp;
		xsp = jj_scanpos;
		if (jj_3R_111()){
			jj_scanpos = xsp;
			if (jj_3R_112())
	private boolean jj_3R_111(){
		if (jj_3R_23())
			return true;
		}
		return false;
	}

	private boolean jj_3R_119(){
		if (jj_3R_142())
	private boolean jj_3R_163(){
		if (jj_3R_49())
			return true;
		return false;
	}

	private boolean jj_3R_118(){
		if (jj_3R_141())
	private boolean jj_3R_64(){
		Token xsp;
		xsp = jj_scanpos;
		if (jj_3R_111()){
			jj_scanpos = xsp;
			if (jj_3R_112())
				return true;
		}
		return false;
	}

@@ -3689,20 +3676,6 @@ public class ADQLParser implements ADQLParserConstants {
		return false;
	}

	private boolean jj_3R_65(){
		if (jj_3R_41())
			return true;
		Token xsp;
		while(true){
			xsp = jj_scanpos;
			if (jj_3R_113()){
				jj_scanpos = xsp;
				break;
			}
		}
		return false;
	}

	private boolean jj_3R_28(){
		if (jj_3R_44())
			return true;
@@ -3724,6 +3697,20 @@ public class ADQLParser implements ADQLParserConstants {
		return false;
	}

	private boolean jj_3R_65(){
		if (jj_3R_41())
			return true;
		Token xsp;
		while(true){
			xsp = jj_scanpos;
			if (jj_3R_113()){
				jj_scanpos = xsp;
				break;
			}
		}
		return false;
	}

	private boolean jj_3R_183(){
		if (jj_scan_token(COMMA))
			return true;
@@ -4857,6 +4844,12 @@ public class ADQLParser implements ADQLParserConstants {
		return false;
	}

	private boolean jj_3_3(){
		if (jj_3R_17())
			return true;
		return false;
	}

	private boolean jj_3R_100(){
		Token xsp;
		xsp = jj_scanpos;
@@ -4880,12 +4873,6 @@ public class ADQLParser implements ADQLParserConstants {
		return false;
	}

	private boolean jj_3_3(){
		if (jj_3R_17())
			return true;
		return false;
	}

	private boolean jj_3R_150(){
		if (jj_3R_41())
			return true;
@@ -5711,6 +5698,34 @@ public class ADQLParser implements ADQLParserConstants {
		return false;
	}

	private boolean jj_3R_165(){
		if (jj_scan_token(NOT))
			return true;
		return false;
	}

	private boolean jj_3R_44(){
		if (jj_scan_token(SELECT))
			return true;
		Token xsp;
		xsp = jj_scanpos;
		if (jj_3R_136())
			jj_scanpos = xsp;
		xsp = jj_scanpos;
		if (jj_3R_137())
			jj_scanpos = xsp;
		if (jj_3R_138())
			return true;
		while(true){
			xsp = jj_scanpos;
			if (jj_3R_139()){
				jj_scanpos = xsp;
				break;
			}
		}
		return false;
	}

	/** Generated Token Manager. */
	public ADQLParserTokenManager token_source;
	SimpleCharStream jj_input_stream;
+0 −1
Original line number Diff line number Diff line
@@ -5,7 +5,6 @@ package adql.parser;
 * Token literal values and constants.
 * Generated by org.javacc.parser.OtherFilesGen#start()
 */
@SuppressWarnings("all")
public interface ADQLParserConstants {

	/** End of File. */
+0 −1
Original line number Diff line number Diff line
@@ -21,7 +21,6 @@ import adql.translator.PostgreSQLTranslator;
import adql.translator.TranslationException;

/** Token Manager. */
@SuppressWarnings("all")
public class ADQLParserTokenManager implements ADQLParserConstants {

	/** Debug output. */
+11 −4
Original line number Diff line number Diff line
@@ -16,7 +16,8 @@ package adql.parser;
 * You should have received a copy of the GNU Lesser General Public License
 * along with ADQLLibrary.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * Copyright 2012 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
 * Copyright 2012-2013 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
 *                       Astronomisches Rechen Institute (ARI)
 */

import adql.db.DBChecker;
@@ -27,13 +28,19 @@ import adql.query.ADQLQuery;
 * 
 * <p>Usually, it consists to check the existence of referenced columns and tables. In this case, one default implementation of this interface can be used: {@link DBChecker}</p>
 * 
 * @author Gr&eacute;gory Mantelet (CDS)
 * @version 08/2011
 * @author Gr&eacute;gory Mantelet (CDS;ARI)
 * @version 1.2 (12/2013)
 */
public interface QueryChecker {

	/**
	 * <p>Checks (non-recursively in sub-queries) the given {@link ADQLQuery}.</p>
	 * <p>Checks the given {@link ADQLQuery}.</p>
	 * 
	 * <p><b>
	 * 	<u>Important note:</u>
	 * 	All subqueries must also be checked when calling this function!
	 * </b></p>
	 * 
	 * <p>If the query is correct, nothing happens. However at the first detected error, a {@link ParseException} is thrown.</p>
	 * 
	 * @param query				The query to check.
Loading