diff --git a/src/adql/db/DBChecker.java b/src/adql/db/DBChecker.java
index 89bb1a25a8feb1b1082eb7acabca1c66213b4495..b83e7fefc3f4f2a37481ef165d4c08e765bcd153 100644
--- a/src/adql/db/DBChecker.java
+++ b/src/adql/db/DBChecker.java
@@ -42,6 +42,7 @@ import adql.parser.QueryChecker;
 import adql.parser.grammar.ParseException;
 import adql.query.ADQLIterator;
 import adql.query.ADQLObject;
+import adql.query.ADQLOrder;
 import adql.query.ADQLQuery;
 import adql.query.ClauseADQL;
 import adql.query.ClauseSelect;
@@ -668,28 +669,38 @@ public class DBChecker implements QueryChecker {
 	}
 
 	/**
-	 * <p>Search all column references inside the given query, resolve them thanks to the given tables' metadata,
-	 * and if there is only one match, attach the matching metadata to them.</p>
+	 * Search all column references inside the given query, resolve them thanks
+	 * to the given tables' metadata, and if there is only one match, attach the
+	 * matching metadata to them.
 	 *
-	 * <b>Management of selected columns' references</b>
+	 * <h4>Management of selected columns' references</h4>
 	 * <p>
-	 * 	A column reference is not only a direct reference to a table column using a column name.
-	 * 	It can also be a reference to an item of the SELECT clause (which will then call a "selected column").
-	 * 	That kind of reference can be either an index (an unsigned integer starting from 1 to N, where N is the
-	 * 	number selected columns), or the name/alias of the column.
+	 * 	A column reference is not only a direct reference to a table column
+	 * 	using a column name. It can also be a reference to an item of the SELECT
+	 * 	clause (which will then call a "selected column"). That kind of
+	 * 	reference can be either an index (an unsigned integer starting from
+	 * 	1 to N, where N is the number selected columns), or the name/alias of
+	 * 	the column.
 	 * </p>
 	 * <p>
-	 * 	These references are also checked, in a second step, in this function. Thus, column metadata are
-	 * 	also attached to them, as common columns.
+	 * 	These references are also checked, in second steps, in this function.
+	 * 	Thus, column metadata are also attached to them, as common columns.
 	 * </p>
 	 *
-	 * @param query			Query in which the existence of tables must be checked.
-	 * @param fathersList	List of all columns available in the father queries and that should be accessed in sub-queries.
-	 *                      Each item of this stack is a list of columns available in each father-level query.
-	 *                   	<i>Note: this parameter is NULL if this function is called with the root/father query as parameter.</i>
+	 * @param query			Query in which the existence of tables must be
+	 *             			checked.
+	 * @param fathersList	List of all columns available in the father queries
+	 *                   	and that should be accessed in sub-queries.
+	 *                      Each item of this stack is a list of columns
+	 *                      available in each father-level query.
+	 *                   	<i><b>Note:</b> this parameter is NULL if this
+	 *                   	function is called with the root/father query as
+	 *                   	parameter.</i>
 	 * @param mapTables		List of all resolved tables.
-	 * @param list			List of column metadata to complete in this function each time a column reference is resolved.
-	 * @param errors		List of errors to complete in this function each time an unknown table or column is encountered.
+	 * @param list			List of column metadata to complete in this function
+	 *            			each time a column reference is resolved.
+	 * @param errors		List of errors to complete in this function each
+	 *              		time an unknown table or column is encountered.
 	 */
 	protected void resolveColumns(final ADQLQuery query, final Stack<SearchColumnList> fathersList, final Map<DBTable, ADQLTable> mapTables, final SearchColumnList list, final UnresolvedIdentifiersException errors) {
 		ISearchHandler sHandler;
@@ -699,12 +710,7 @@ public class DBChecker implements QueryChecker {
 		sHandler.search(query);
 		for(ADQLObject result : sHandler) {
 			try {
-				ADQLColumn adqlColumn = (ADQLColumn)result;
-				// resolve the column:
-				DBColumn dbColumn = resolveColumn(adqlColumn, list, fathersList);
-				// link with the matched DBColumn:
-				adqlColumn.setDBLink(dbColumn);
-				adqlColumn.setAdqlTable(mapTables.get(dbColumn.getTable()));
+				resolveColumn((ADQLColumn)result, list, mapTables, fathersList);
 			} catch(ParseException pe) {
 				errors.addException(pe);
 			}
@@ -712,26 +718,16 @@ public class DBChecker implements QueryChecker {
 
 		// Check the GROUP BY items:
 		ClauseSelect select = query.getSelect();
-		sHandler = new SearchColumnHandler();
-		sHandler.search(query.getGroupBy());
-		for(ADQLObject result : sHandler) {
-			try {
-				ADQLColumn adqlColumn = (ADQLColumn)result;
-				// resolve the column:
-				DBColumn dbColumn = checkGroupByItem(adqlColumn, select, list);
-				// link with the matched DBColumn:
-				if (dbColumn != null) {
-					adqlColumn.setDBLink(dbColumn);
-					adqlColumn.setAdqlTable(mapTables.get(dbColumn.getTable()));
-				}
-			} catch(ParseException pe) {
-				errors.addException(pe);
-			}
-		}
+		checkGroupBy(query.getGroupBy(), select, list, mapTables, fathersList, errors);
+
+		// Check the ORDER BY items:
+		checkOrderBy(query.getOrderBy(), select, list, mapTables, fathersList, errors);
 
-		// Check the correctness of all column references (= references to selected columns):
-		/* Note: no need to provide the father tables when resolving column references,
-		 *       because no father column can be used in ORDER BY. */
+		/* Check the correctness of all column references (= references to
+		 * selected columns):
+		 *
+		 * NOTE: no need to provide the father tables when resolving column
+		 *       references, because no father column can be used in ORDER BY. */
 		sHandler = new SearchColReferenceHandler();
 		sHandler.search(query);
 		for(ADQLObject result : sHandler) {
@@ -750,24 +746,78 @@ public class DBChecker implements QueryChecker {
 	}
 
 	/**
-	 * <p>Resolve the given column, that's to say search for the corresponding {@link DBColumn}.</p>
+	 * Resolve the given column (i.e. search for the corresponding
+	 * {@link DBColumn}) and update the given {@link ADQLColumn} with these
+	 * found DB metadata.
 	 *
 	 * <p>
-	 * 	The third parameter is used only if this function is called inside a sub-query. In this case,
-	 * 	the 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 (fathersList).
+	 * 	The fourth parameter is used only if this function is called inside a
+	 * 	sub-query. In this case, the 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 (fathersList).
 	 * </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 queries and that should be accessed in sub-queries.
-	 *                      Each item of this stack is a list of columns available in each father-level query.
-	 *                   	<i>Note: this parameter is NULL if this function is called with the root/father query as parameter.</i>
+	 * @param mapTables		List of all resolved tables.
+	 * @param fathersList	List of all columns available in the father queries
+	 *                   	and that should be accessed in sub-queries.
+	 *                      Each item of this stack is a list of columns
+	 *                      available in each father-level query.
+	 *                   	<i>Note: this parameter is NULL if this function is
+	 *                   	called with the root/father query as parameter.</i>
 	 *
-	 * @return 				The corresponding {@link DBColumn} if found. Otherwise an exception is thrown.
+	 * @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.
+	 * @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.
+	 *
+	 * @see #resolveColumn(ADQLColumn, SearchColumnList, Stack)
+	 *
+	 * @since 2.0
+	 */
+	protected final DBColumn resolveColumn(final ADQLColumn column, final SearchColumnList dbColumns, final Map<DBTable, ADQLTable> mapTables, Stack<SearchColumnList> fathersList) throws ParseException {
+		// Resolve the column:
+		DBColumn dbColumn = resolveColumn(column, dbColumns, fathersList);
+
+		// Link the given ADQLColumn with the matched DBColumn:
+		column.setDBLink(dbColumn);
+		if (mapTables != null)
+			column.setAdqlTable(mapTables.get(dbColumn.getTable()));
+
+		return dbColumn;
+	}
+
+	/**
+	 * Resolve the given column, that's to say search for the corresponding
+	 * {@link DBColumn}.
+	 *
+	 * <p>
+	 * 	The third parameter is used only if this function is called inside a
+	 * 	sub-query. In this case, the 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 (fathersList).
+	 * </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 queries
+	 *                   	and that should be accessed in sub-queries.
+	 *                      Each item of this stack is a list of columns
+	 *                      available in each father-level query.
+	 *                   	<i>Note: this parameter is NULL if this function is
+	 *                   	called with the root/father query as parameter.</i>
+	 *
+	 * @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, Stack<SearchColumnList> fathersList) throws ParseException {
 		List<DBColumn> foundColumns = dbColumns.search(column);
@@ -794,24 +844,156 @@ public class DBChecker implements QueryChecker {
 	}
 
 	/**
-	 * Check whether the given column corresponds to a selected item's alias or to an existing column.
+	 * Check and resolve all columns (or column references) inside the given
+	 * GROUP BY clause.
+	 *
+	 * @param groupBy		The GROUP BY to check.
+	 * @param select		The SELECT clause (and all its selected items).
+	 * @param list			List of all available {@link DBColumn}s.
+	 * @param mapTables		List of all resolved tables.
+	 * @param fathersList	List of all columns available in the father queries
+	 *                   	and that should be accessed in sub-queries.
+	 *                   	Each item of this stack is a list of columns
+	 *                   	available in each father-level query.
+	 *                   	<i>Note: this parameter is NULL if this function is
+	 *                   	called with the root/father query as parameter.</i>
+	 * @param errors		List of errors to complete in this function each
+	 *              		time an unknown table or column is encountered.
+	 *
+	 * @since 2.0
+	 */
+	protected void checkGroupBy(final ClauseADQL<ADQLOperand> groupBy, final ClauseSelect select, final SearchColumnList list, final Map<DBTable, ADQLTable> mapTables, Stack<SearchColumnList> fathersList, final UnresolvedIdentifiersException errors) {
+		for(ADQLOperand obj : groupBy) {
+			try {
+				if (obj instanceof ADQLColumn) {
+					ADQLColumn adqlColumn = (ADQLColumn)obj;
+					/* resolve the column either as a selected column reference
+					 * or as a normal column: */
+					if (adqlColumn.getTableName() == null)
+						resolveColumnNameReference(adqlColumn, select, list, mapTables);
+					else
+						resolveColumn(adqlColumn, list, fathersList);
+				} else {
+					ISearchHandler sHandler = new SearchColumnHandler();
+					sHandler.search(obj);
+					for(ADQLObject result : sHandler) {
+						try {
+							resolveColumn((ADQLColumn)result, list, mapTables, fathersList);
+						} catch(ParseException pe) {
+							errors.addException(pe);
+						}
+					}
+				}
+			} catch(ParseException pe) {
+				errors.addException(pe);
+			}
+		}
+	}
+
+	/**
+	 * Check and resolve all columns (or column references) inside the given
+	 * ORDER BY clause.
+	 *
+	 * @param orderBy		The ORDER BY to check.
+	 * @param select		The SELECT clause (and all its selected items).
+	 * @param list			List of all available {@link DBColumn}s.
+	 * @param mapTables		List of all resolved tables.
+	 * @param fathersList	List of all columns available in the father queries
+	 *                   	and that should be accessed in sub-queries.
+	 *                   	Each item of this stack is a list of columns
+	 *                   	available in each father-level query.
+	 *                   	<i>Note: this parameter is NULL if this function is
+	 *                   	called with the root/father query as parameter.</i>
+	 * @param errors		List of errors to complete in this function each
+	 *              		time an unknown table or column is encountered.
+	 *
+	 * @since 2.0
+	 */
+	protected void checkOrderBy(final ClauseADQL<ADQLOrder> orderBy, final ClauseSelect select, final SearchColumnList list, final Map<DBTable, ADQLTable> mapTables, Stack<SearchColumnList> fathersList, final UnresolvedIdentifiersException errors) {
+		for(ADQLObject obj : orderBy) {
+			try {
+				ADQLOrder order = (ADQLOrder)obj;
+				if (order.getExpression() != null) {
+					ADQLOperand expr = order.getExpression();
+					if (expr instanceof ADQLColumn) {
+						ADQLColumn adqlColumn = (ADQLColumn)expr;
+						/* resolve the column either as a selected column reference
+						 * or as a normal column: */
+						if (adqlColumn.getTableName() == null)
+							resolveColumnNameReference(adqlColumn, select, list, mapTables);
+						else
+							resolveColumn(adqlColumn, list, fathersList);
+					} else {
+						ISearchHandler sHandler = new SearchColumnHandler();
+						sHandler.search(expr);
+						for(ADQLObject result : sHandler) {
+							try {
+								resolveColumn((ADQLColumn)result, list, mapTables, fathersList);
+							} catch(ParseException pe) {
+								errors.addException(pe);
+							}
+						}
+					}
+				}
+			} catch(ParseException pe) {
+				errors.addException(pe);
+			}
+		}
+	}
+
+	/**
+	 * Check whether the given column corresponds to a selected item's alias or
+	 * to an existing column.
 	 *
 	 * @param col			The column to check.
 	 * @param select		The SELECT clause of the ADQL query.
 	 * @param dbColumns		The list of all available columns.
 	 *
-	 * @return 	The corresponding {@link DBColumn} if this column corresponds to an existing column,
+	 * @return 	The corresponding {@link DBColumn} if this column corresponds to
+	 *        	an existing column,
 	 *        	<i>NULL</i> otherwise.
 	 *
-	 * @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.
+	 * @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.
 	 *
 	 * @see ClauseSelect#searchByAlias(String)
 	 * @see #resolveColumn(ADQLColumn, SearchColumnList, Stack)
 	 *
 	 * @since 1.4
+	 *
+	 * @deprecated	Since 2.0, this function has been renamed into
+	 *            	{@link #resolveColumnNameReference(ADQLColumn, ClauseSelect, SearchColumnList, Map)}.
 	 */
-	protected DBColumn checkGroupByItem(final ADQLColumn col, final ClauseSelect select, final SearchColumnList dbColumns) throws ParseException {
+	@Deprecated
+	protected final DBColumn checkGroupByItem(final ADQLColumn col, final ClauseSelect select, final SearchColumnList dbColumns) throws ParseException {
+		return resolveColumnNameReference(col, select, dbColumns, null);
+	}
+
+	/**
+	 * Check whether the given column corresponds to a selected item's alias or
+	 * to an existing column.
+	 *
+	 * @param col			The column to check.
+	 * @param select		The SELECT clause of the ADQL query.
+	 * @param dbColumns		The list of all available columns.
+	 *
+	 * @return 	The corresponding {@link DBColumn} if this column corresponds to
+	 *        	an existing column,
+	 *        	<i>NULL</i> otherwise.
+	 *
+	 * @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.
+	 *
+	 * @see ClauseSelect#searchByAlias(String)
+	 * @see #resolveColumn(ADQLColumn, SearchColumnList, Map, Stack)
+	 *
+	 * @since 2.0
+	 */
+	protected DBColumn resolveColumnNameReference(final ADQLColumn col, final ClauseSelect select, final SearchColumnList dbColumns, final Map<DBTable, ADQLTable> mapTables) throws ParseException {
 		/* If the column name is not qualified, it may be a SELECT-item's alias.
 		 * So, try resolving the name as an alias.
 		 * If it fails, perform the normal column resolution.*/
@@ -822,51 +1004,36 @@ public class DBChecker implements QueryChecker {
 			else if (founds.size() > 1)
 				throw new UnresolvedColumnException(col, founds.get(0).getAlias(), founds.get(1).getAlias());
 		}
-		return resolveColumn(col, dbColumns, null);
+		return resolveColumn(col, dbColumns, mapTables, null);
 	}
 
 	/**
-	 * Check whether the given column reference corresponds to a selected item (column or an expression with an alias)
-	 * or to an existing column.
+	 * Check whether the given column reference corresponds to a selected item
+	 * (column or an expression with an alias).
 	 *
 	 * @param colRef		The column reference which must be checked.
 	 * @param select		The SELECT clause of the ADQL query.
 	 * @param dbColumns		The list of all available columns.
 	 *
-	 * @return 		The corresponding {@link DBColumn} if this reference is actually the name of a column, <i>null</i> otherwise.
+	 * @return 		The corresponding {@link DBColumn} if this reference is
+	 *        		actually referencing a selected column,
+	 *        		NULL otherwise.
 	 *
-	 * @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.
-	 *
-	 * @see ClauseSelect#searchByAlias(String)
-	 * @see #resolveColumn(ADQLColumn, SearchColumnList, Stack)
+	 * @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 checkColumnReference(final ColumnReference colRef, final ClauseSelect select, final SearchColumnList dbColumns) throws ParseException {
-		if (colRef.isIndex()) {
-			int index = colRef.getColumnIndex();
-			if (index > 0 && index <= select.size()) {
-				SelectItem item = select.get(index - 1);
-				if (item.getOperand() instanceof ADQLColumn)
-					return ((ADQLColumn)item.getOperand()).getDBLink();
-				else
-					return null;
-			} else
-				throw new ParseException("Column index out of bounds: " + index + " (must be between 1 and " + select.size() + ") !", colRef.getPosition());
-		} else {
-			ADQLColumn col = new ADQLColumn(null, colRef.getColumnName());
-			col.setCaseSensitive(colRef.isCaseSensitive());
-			col.setPosition(colRef.getPosition());
-
-			// search among the select_item aliases:
-			List<SelectItem> founds = select.searchByAlias(colRef.getColumnName(), colRef.isCaseSensitive());
-			if (founds.size() == 1)
+		int index = colRef.getColumnIndex();
+		if (index > 0 && index <= select.size()) {
+			SelectItem item = select.get(index - 1);
+			if (item.getOperand() instanceof ADQLColumn)
+				return ((ADQLColumn)item.getOperand()).getDBLink();
+			else
 				return null;
-			else if (founds.size() > 1)
-				throw new UnresolvedColumnException(col, founds.get(0).getAlias(), founds.get(1).getAlias());
-
-			// check the corresponding column:
-			return resolveColumn(col, dbColumns, null);
-		}
+		} else
+			throw new ParseException("Column index out of bounds: " + index + " (must be between 1 and " + select.size() + ") !", colRef.getPosition());
 	}
 
 	/**
@@ -1426,21 +1593,31 @@ public class DBChecker implements QueryChecker {
 	/* *************** */
 
 	/**
-	 * Lets searching all {@link ADQLColumn} in the given object, EXCEPT in the GROUP BY clause.
+	 * Lets searching all {@link ADQLColumn} in the given object,
+	 * EXCEPT in the GROUP BY and ORDER BY clauses.
 	 *
 	 * <p>
-	 * 	{@link ADQLColumn}s of the GROUP BY may be aliases and so, they can not be checked
-	 *	exactly as a normal column.
+	 * 	{@link ADQLColumn}s of the GROUP BY and ORDER BY may be aliases and so,
+	 * 	they can not be checked exactly as a normal column.
 	 * </p>
 	 *
-	 * @author Gr&eacute;gory Mantelet (ARI)
-	 * @version 1.4 (05/2017)
+	 * <p>
+	 * 	{@link ADQLColumn} of a {@link ColumnReference} may be an alias, they
+	 * 	can not be checked exactly as a normal column.
+	 * </p>
+	 *
+	 * @author Gr&eacute;gory Mantelet (ARI;CDS)
+	 * @version 2.0 (08/2019)
 	 * @since 1.4
 	 */
 	private static class SearchColumnOutsideGroupByHandler extends SearchColumnHandler {
 		@Override
 		protected boolean goInto(final ADQLObject obj) {
-			return !(obj instanceof ClauseADQL<?> && ((ClauseADQL<?>)obj).getName() != null && ((ClauseADQL<?>)obj).getName().equalsIgnoreCase("GROUP BY")) && super.goInto(obj);
+			if (obj instanceof ClauseADQL<?> && ((ClauseADQL<?>)obj).getName() != null) {
+				ClauseADQL<?> clause = (ClauseADQL<?>)obj;
+				return !(clause.getName().equalsIgnoreCase("GROUP BY") || clause.getName().equalsIgnoreCase("ORDER BY"));
+			} else
+				return super.goInto(obj);
 		}
 	}
 
diff --git a/src/adql/parser/ADQLParser.java b/src/adql/parser/ADQLParser.java
index dec0fe5b8ec9c3d0893667f8494507e8d1e7b79c..0df8211d4ac4114ef1d8013d8b8fc97bf42a9466 100644
--- a/src/adql/parser/ADQLParser.java
+++ b/src/adql/parser/ADQLParser.java
@@ -50,7 +50,6 @@ import adql.query.ClauseADQL;
 import adql.query.ClauseConstraints;
 import adql.query.ClauseSelect;
 import adql.query.from.FromContent;
-import adql.query.operand.ADQLColumn;
 import adql.query.operand.ADQLOperand;
 import adql.query.operand.StringConstant;
 import adql.query.operand.function.geometry.BoxFunction;
@@ -1019,7 +1018,7 @@ public class ADQLParser {
 	 *
 	 * @throws ParseException	If the syntax of the given clause is incorrect.
 	 */
-	public final ClauseADQL<ADQLColumn> parseGroupBy(java.lang.String adql) throws ParseException {
+	public final ClauseADQL<ADQLOperand> parseGroupBy(java.lang.String adql) throws ParseException {
 		// Reset the parser with the string to parse:
 		try {
 			grammarParser.reset(new java.io.ByteArrayInputStream(adql.getBytes()));
diff --git a/src/adql/parser/ADQLQueryFactory.java b/src/adql/parser/ADQLQueryFactory.java
index fb78f4c15279f0f896da4fb039398402a616aa9e..e6558e66cdfc57526aa691244c53de881ee09c95 100644
--- a/src/adql/parser/ADQLQueryFactory.java
+++ b/src/adql/parser/ADQLQueryFactory.java
@@ -60,11 +60,11 @@ import adql.query.operand.OperationType;
 import adql.query.operand.StringConstant;
 import adql.query.operand.WrappedOperand;
 import adql.query.operand.function.DefaultUDF;
+import adql.query.operand.function.InUnitFunction;
 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.InUnitFunction;
 import adql.query.operand.function.UserDefinedFunction;
 import adql.query.operand.function.geometry.AreaFunction;
 import adql.query.operand.function.geometry.BoxFunction;
@@ -459,18 +459,30 @@ public class ADQLQueryFactory {
 	@Deprecated
 	public ADQLOrder createOrder(final int ind, final boolean desc, final TextPosition position) throws Exception {
 		ADQLOrder order = new ADQLOrder(ind, desc);
-		if (order != null)
-			order.setPosition(position);
+		order.setPosition(position);
 		return order;
 	}
 
+	/**
+	 * @deprecated	Since 2.0, a column reference can be a qualified
+	 *            	column (i.e. an {@link ADQLColumn}). You should use
+	 *            	{@link #createOrder(ADQLOperand, boolean)} instead.
+	 */
+	@Deprecated
 	public ADQLOrder createOrder(final IdentifierItem colName, final boolean desc) throws Exception {
 		ADQLOrder order = new ADQLOrder(colName.identifier, desc);
-		if (order != null)
-			order.setCaseSensitive(colName.caseSensitivity);
+		((ADQLColumn)order.getExpression()).setCaseSensitive(colName.caseSensitivity);
+		((ADQLColumn)order.getExpression()).setPosition(colName.position);
 		return order;
 	}
 
+	/**
+	 * @since 2.0
+	 */
+	public ADQLOrder createOrder(final ADQLOperand expr, final boolean desc) throws Exception {
+		return new ADQLOrder(expr, desc);
+	}
+
 	/**
 	 * @deprecated since 1.4 ; Former version's mistake: an ORDER BY item is
 	 *                         either a regular/delimited column name or an
@@ -481,33 +493,14 @@ public class ADQLQueryFactory {
 	@Deprecated
 	public ADQLOrder createOrder(final IdentifierItems idItems, final boolean desc) throws Exception {
 		ADQLOrder order = new ADQLOrder(idItems.join("."), desc);
-		if (order != null)
-			order.setCaseSensitive(idItems.getColumnCaseSensitivity());
+		order.getColumnReference().setCaseSensitive(idItems.getColumnCaseSensitivity());
+		order.getColumnReference().setPosition(idItems.getPosition());
 		return order;
 	}
 
-	public ColumnReference createColRef(final IdentifierItem idItem) throws Exception {
-		ColumnReference colRef = new ColumnReference(idItem.identifier);
-		if (colRef != null) {
-			colRef.setPosition(idItem.position);
-			colRef.setCaseSensitive(idItem.caseSensitivity);
-		}
-		return colRef;
-	}
-
-	public ColumnReference createColRef(final IdentifierItems idItems) throws Exception {
-		ColumnReference colRef = new ColumnReference(idItems.join("."));
-		if (colRef != null) {
-			colRef.setPosition(idItems.getPosition());
-			colRef.setCaseSensitive(idItems.getColumnCaseSensitivity());
-		}
-		return colRef;
-	}
-
 	public ColumnReference createColRef(final int index, final TextPosition position) throws Exception {
 		ColumnReference colRef = new ColumnReference(index);
-		if (colRef != null)
-			colRef.setPosition(position);
+		colRef.setPosition(position);
 		return colRef;
 	}
 
diff --git a/src/adql/parser/grammar/adqlGrammar200.jj b/src/adql/parser/grammar/adqlGrammar200.jj
index c622b8a35901499ed45ef3b1297e71b075cf1733..d1e6b4a78806e6e59c7355ceeb42749b5340c335 100644
--- a/src/adql/parser/grammar/adqlGrammar200.jj
+++ b/src/adql/parser/grammar/adqlGrammar200.jj
@@ -556,10 +556,10 @@ void Where(): {ClauseConstraints where = query.getWhere(); ADQLConstraint condit
 	}
 }
 
-void GroupBy(): {ClauseADQL<ADQLColumn> groupBy = query.getGroupBy(); ADQLColumn colRef = null; Token start;} {
+void GroupBy(): {ClauseADQL<ADQLOperand> groupBy = query.getGroupBy(); ADQLColumn colRef = null; Token start;} {
 	start=<GROUP> <BY> colRef=Column() { groupBy.add(colRef); }
 	( <COMMA> colRef=Column() { groupBy.add(colRef); } )*
-	{ groupBy.setPosition(new TextPosition(start.beginLine, start.beginColumn, colRef.getPosition().endLine, colRef.getPosition().endColumn)); }
+	{ groupBy.setPosition(new TextPosition(new TextPosition(start), colRef.getPosition())); }
 }
 
 void Having(): {ClauseConstraints having = query.getHaving(); Token start;} {
@@ -641,10 +641,17 @@ ADQLOrder OrderItem(): {IdentifierItem identifier = null; Token ind = null, desc
 			ADQLOrder order = null;
 			if (identifier != null){
 				order = queryFactory.createOrder(identifier, desc!=null);
-				order.setPosition(identifier.position);
+				if (desc == null)
+					order.setPosition(identifier.position);
+				else
+					order.setPosition(new TextPosition(identifier.position, new TextPosition(desc)));
 			}else{
 				order = queryFactory.createOrder(Integer.parseInt(ind.image), desc!=null);
-				order.setPosition(new TextPosition(ind));
+				order.getColumnReference().setPosition(new TextPosition(ind));
+				if (desc == null)
+					order.setPosition(new TextPosition(ind));
+				else
+					order.setPosition(new TextPosition(ind, desc));
 			}
 			return order;
 		}catch(Exception ex){
@@ -853,14 +860,16 @@ ADQLOperand ValueExpression(): {ADQLOperand valueExpr = null; Token left, right;
 		| LOOKAHEAD(3) valueExpr=Factor()
 
 		/* At this position in this switch, all possibilities (including
-		 * Column()) have already been tested and failed.
+		 * Column() and NumericFunction) have already been tested and failed.
 		 * 
-		 * So, this final choice actually aims to throw an error set with the
+		 * So, these final choices actually aim to throw an error set with the
 		 * current token and with an error message implying that a column name
-		 * was expected (which is generally the case in an ADQL query).
-		 *
-		 * Note: This choice will generally be reached if an unexpected ADQL/SQL
-		 *       word is ending the query. */
+		 * was expected (which is generally the case in an ADQL query) or that
+		 * the parameters of the numeric function are incorrect. */
+		| LOOKAHEAD(2) valueExpr=NumericFunction()
+		/*
+		 * Note: Besides, this particular choice will generally be reached if an
+		 *       unexpected ADQL/SQL word is ending the query. */
 		| valueExpr=Column() )
 		{return valueExpr;}
 	}catch(Exception ex){
@@ -1024,8 +1033,7 @@ ClauseConstraints ConditionsList(ClauseConstraints clause): {ADQLConstraint cons
 }
 
 ADQLConstraint Constraint(): {ADQLConstraint constraint =  null; Token start, end;} {
-	(LOOKAHEAD(<EXISTS> | ValueExpression()) constraint=Predicate()
-	| (
+	(LOOKAHEAD(<LEFT_PAR> [<NOT>] Predicate())
 		start=<LEFT_PAR>
 		{
 			try{
@@ -1037,7 +1045,9 @@ ADQLConstraint Constraint(): {ADQLConstraint constraint =  null; Token start, en
 		ConditionsList((ConstraintsGroup)constraint)
 		end=<RIGHT_PAR>
 		{ ((ConstraintsGroup)constraint).setPosition(new TextPosition(start, end)); }
-	))
+	|
+		 constraint=Predicate()
+	)
 	{return constraint;}
 }
 
diff --git a/src/adql/parser/grammar/adqlGrammar201.jj b/src/adql/parser/grammar/adqlGrammar201.jj
index 53bb5f81709aec53a6807f83201f9c97259d292b..7ee135a2f5a80d3c0ad37017e8bbae4b48f494f1 100644
--- a/src/adql/parser/grammar/adqlGrammar201.jj
+++ b/src/adql/parser/grammar/adqlGrammar201.jj
@@ -577,10 +577,10 @@ void Where(): {ClauseConstraints where = query.getWhere(); ADQLConstraint condit
 	}
 }
 
-void GroupBy(): {ClauseADQL<ADQLColumn> groupBy = query.getGroupBy(); ADQLColumn colRef = null; Token start;} {
-	start=<GROUP> <BY> colRef=Column() { groupBy.add(colRef); }
-	( <COMMA> colRef=Column() { groupBy.add(colRef); } )*
-	{ groupBy.setPosition(new TextPosition(start.beginLine, start.beginColumn, colRef.getPosition().endLine, colRef.getPosition().endColumn)); }
+void GroupBy(): {ClauseADQL<ADQLOperand> groupBy = query.getGroupBy(); ADQLOperand item = null; Token start;} {
+	start=<GROUP> <BY> item=ValueExpression() { groupBy.add(item); }
+	( <COMMA> item=ValueExpression() { groupBy.add(item); } )*
+	{ groupBy.setPosition(new TextPosition(new TextPosition(start), item.getPosition())); }
 }
 
 void Having(): {ClauseConstraints having = query.getHaving(); Token start;} {
@@ -670,18 +670,30 @@ ADQLColumn Column(): {IdentifierItems identifiers;} {
 	}
 }
 
-ADQLOrder OrderItem(): {IdentifierItem identifier = null; Token ind = null, desc = null;}{
-	(identifier=Identifier() | ind=<UNSIGNED_INTEGER>) (<ASC> | desc=<DESC>)?
+ADQLOrder OrderItem(): {ADQLOperand expr = null; Token ind = null, desc = null;}{
+	expr=ValueExpression()
+	{
+		if (expr instanceof NumericConstant && token.kind == UNSIGNED_INTEGER)
+			ind = token;
+	}
+	(<ASC> | desc=<DESC>)?
 	{
 		try{
 			ADQLOrder order = null;
-			if (identifier != null){
-				order = queryFactory.createOrder(identifier, desc!=null);
-				order.setPosition(identifier.position);
-			}else{
+			if (ind != null){
 				order = queryFactory.createOrder(Integer.parseInt(ind.image), desc!=null);
-				order.setPosition(new TextPosition(ind));
-			}
+				order.getColumnReference().setPosition(new TextPosition(ind));
+				if (desc == null)
+					order.setPosition(new TextPosition(ind));
+				else
+					order.setPosition(new TextPosition(ind, desc));
+			}else{
+				order = queryFactory.createOrder(expr, desc!=null);
+				if (desc == null)
+					order.setPosition(expr.getPosition());
+				else
+					order.setPosition(new TextPosition(expr.getPosition(), new TextPosition(desc)));
+			} 
 			return order;
 		}catch(Exception ex){
 			throw generateParseException(ex);
@@ -1083,8 +1095,7 @@ ClauseConstraints ConditionsList(ClauseConstraints clause): {ADQLConstraint cons
 }
 
 ADQLConstraint Constraint(): {ADQLConstraint constraint =  null; Token start, end;} {
-	(LOOKAHEAD(<EXISTS> | ValueExpression()) constraint=Predicate()
-	| (
+	(LOOKAHEAD(<LEFT_PAR> [<NOT>] Predicate())
 		start=<LEFT_PAR>
 		{
 			try{
@@ -1096,7 +1107,9 @@ ADQLConstraint Constraint(): {ADQLConstraint constraint =  null; Token start, en
 		ConditionsList((ConstraintsGroup)constraint)
 		end=<RIGHT_PAR>
 		{ ((ConstraintsGroup)constraint).setPosition(new TextPosition(start, end)); }
-	))
+	|
+		 constraint=Predicate()
+	)
 	{return constraint;}
 }
 
diff --git a/src/adql/query/ADQLOrder.java b/src/adql/query/ADQLOrder.java
index 744a0e876a54c48c83eeb77cae5fd29f949c91bb..b43c95fd3b81938b43b3022e86d5702127cac1ca 100644
--- a/src/adql/query/ADQLOrder.java
+++ b/src/adql/query/ADQLOrder.java
@@ -16,17 +16,47 @@ package adql.query;
  * 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-2019 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
  */
 
+import java.util.NoSuchElementException;
+
+import adql.parser.feature.LanguageFeature;
+import adql.query.operand.ADQLColumn;
+import adql.query.operand.ADQLOperand;
+
 /**
  * Represents an item of the ORDER BY list: that's to say a column reference
- * plus a sorting indication (ASC, DESC).
+ * or a value expression, and an optional sorting indication (ASC, DESC).
  *
  * @author Gr&eacute;gory Mantelet (CDS)
- * @version 06/2011
+ * @version 2.0 (08/2019)
  */
-public class ADQLOrder extends ColumnReference {
+public class ADQLOrder implements ADQLObject {
+
+	/** Description of this ADQL Feature.
+	 * @since 2.0 */
+	public static final LanguageFeature FEATURE = new LanguageFeature(null, "ORDER_BY_ITEM", false, "Column reference or expression on which the query result must be ordered.");
+
+	/** Position in the original ADQL query string.
+	 * @since 2.0 */
+	private TextPosition position = null;
+
+	/** Reference to the column on which the query result must be ordered.
+	 * <p><i><b>Important note:</b>
+	 * 	If NULL, this ORDER BY is done on a value expression.
+	 * 	In such case, see {@link #expression}.
+	 * </i></p>
+	 * @since 2.0 */
+	protected ColumnReference colRef = null;
+
+	/** Value on which the query result must be ordered.
+	 * <p><i><b>Important note:</b>
+	 * 	If NULL, this ORDER BY is done on a column reference.
+	 * 	In such case, see {@link #colRef}.
+	 * </i></p>
+	 * @since 2.0 */
+	protected ADQLOperand expression = null;
 
 	/** Gives an indication about how to order the results of a query.
 	 * (<code>true</code> for DESCending, <code>false</code> for ASCending) */
@@ -40,9 +70,9 @@ public class ADQLOrder extends ColumnReference {
 	 *
 	 * @throws ArrayIndexOutOfBoundsException	If the index is less or equal 0.
 	 *
-	 * @see ADQLOrder#ADQLOrder(int, boolean)
+	 * @see #ADQLOrder(int, boolean)
 	 */
-	public ADQLOrder(int colIndex) throws ArrayIndexOutOfBoundsException {
+	public ADQLOrder(final int colIndex) throws ArrayIndexOutOfBoundsException {
 		this(colIndex, false);
 	}
 
@@ -55,10 +85,11 @@ public class ADQLOrder extends ColumnReference {
 	 *            		<code>false</code> means ASCending order.
 	 *
 	 * @throws ArrayIndexOutOfBoundsException	If the index is less or equal 0.
+	 *
+	 * @see ColumnReference#ColumnReference(int) ColumnReference(int)
 	 */
-	public ADQLOrder(int colIndex, boolean desc) throws ArrayIndexOutOfBoundsException {
-		super(colIndex);
-		descSorting = desc;
+	public ADQLOrder(final int colIndex, final boolean desc) throws ArrayIndexOutOfBoundsException {
+		setOrder(colIndex, desc);
 	}
 
 	/**
@@ -70,9 +101,9 @@ public class ADQLOrder extends ColumnReference {
 	 * @throws NullPointerException	If the given name is NULL
 	 *                             	or is an empty string.
 	 *
-	 * @see ADQLOrder#ADQLOrder(String, boolean)
+	 * @see #ADQLOrder(String, boolean)
 	 */
-	public ADQLOrder(String colName) throws NullPointerException {
+	public ADQLOrder(final String colName) throws NullPointerException {
 		this(colName, false);
 	}
 
@@ -86,19 +117,62 @@ public class ADQLOrder extends ColumnReference {
 	 *
 	 * @throws NullPointerException	If the given name is NULL
 	 *                             	or is an empty string.
+	 *
+	 * @see ColumnReference#ColumnReference(String) ColumnReference(String)
+	 *
+	 * @deprecated	Since ADQL-2.1, a column reference can be a qualified
+	 *            	column (i.e. an {@link ADQLColumn}). You should use
+	 *            	{@link #ADQLOrder(ADQLOperand)} instead.
 	 */
-	public ADQLOrder(String colName, boolean desc) throws NullPointerException {
-		super(colName);
-		descSorting = desc;
+	@Deprecated
+	public ADQLOrder(final String colName, final boolean desc) throws NullPointerException {
+		this(new ADQLColumn(null, colName), desc);
+	}
+
+	/**
+	 * Builds an order indication with the expression on which an ASCending
+	 * ordering will be done.
+	 *
+	 * @param expr	The expression to order on.
+	 *
+	 * @throws NullPointerException	If the given expression is NULL.
+	 *
+	 * @see #ADQLOrder(ADQLOperand)
+	 *
+	 * @since 2.0
+	 */
+	public ADQLOrder(final ADQLOperand expr) throws NullPointerException {
+		this(expr, false);
+	}
+
+	/**
+	 * Builds an order indication with the expression on which the specified
+	 * ordering will be done.
+	 *
+	 * @param expr	The expression to order on.
+	 * @param desc	<code>true</code> means DESCending order,
+	 *            	<code>false</code> means ASCending order.
+	 *
+	 * @throws NullPointerException	If the given expression is NULL.
+	 *
+	 * @since 2.0
+	 */
+	public ADQLOrder(final ADQLOperand expr, final boolean desc) throws NullPointerException {
+		setOrder(expr, desc);
 	}
 
 	/**
 	 * Builds an ORDER BY item by copying the given one.
 	 *
 	 * @param toCopy	The ORDER BY item to copy.
+	 *
+	 * @throws Exception	If the copy failed.
 	 */
-	public ADQLOrder(ADQLOrder toCopy) {
-		super(toCopy);
+	public ADQLOrder(ADQLOrder toCopy) throws Exception {
+		if (toCopy.colRef != null)
+			colRef = (ColumnReference)toCopy.colRef.getCopy();
+		if (toCopy.expression != null)
+			expression = (ADQLOperand)toCopy.expression.getCopy();
 		descSorting = toCopy.descSorting;
 	}
 
@@ -112,6 +186,28 @@ public class ADQLOrder extends ColumnReference {
 		return descSorting;
 	}
 
+	/**
+	 * Get the reference of column on which the query result will be ordered.
+	 *
+	 * @return	The set column reference. <i>Might be NULL.</i>
+	 *
+	 * @since 2.0
+	 */
+	public final ColumnReference getColumnReference() {
+		return colRef;
+	}
+
+	/**
+	 * Get the expression on which the query result will be ordered.
+	 *
+	 * @return	The set expression. <i>Might be NULL.</i>
+	 *
+	 * @since 2.0
+	 */
+	public final ADQLOperand getExpression() {
+		return expression;
+	}
+
 	/**
 	 * Updates the current order indication.
 	 *
@@ -122,11 +218,12 @@ public class ADQLOrder extends ColumnReference {
 	 * @throws IndexOutOfBoundsException	If the given index is less
 	 *                                  	or equal 0.
 	 */
-	public void setOrder(int colIndex, boolean desc) throws ArrayIndexOutOfBoundsException {
+	public void setOrder(final int colIndex, final boolean desc) throws ArrayIndexOutOfBoundsException {
 		if (colIndex <= 0)
 			throw new ArrayIndexOutOfBoundsException("Impossible to make a reference to the " + colIndex + "th column: a column index must be greater or equal 1!");
 
-		setColumnIndex(colIndex);
+		colRef = new ColumnReference(colIndex);
+		expression = null;
 		descSorting = desc;
 	}
 
@@ -139,13 +236,41 @@ public class ADQLOrder extends ColumnReference {
 	 *
 	 * @throws NullPointerException	If the given name is NULL
 	 *                             	or is an empty string.
+	 *
+	 * @deprecated	Since ADQL-2.1, a column reference can be a qualified
+	 *            	column (i.e. an {@link ADQLColumn}). You should use
+	 *            	{@link #setOrder(ADQLOperand, boolean)} instead.
 	 */
-	public void setOrder(String colName, boolean desc) throws NullPointerException {
+	@Deprecated
+	public void setOrder(final String colName, final boolean desc) throws NullPointerException {
 		if (colName == null)
 			throw new NullPointerException("Impossible to make a reference: the given name is null or is an empty string!");
 
-		setColumnName(colName);
+		colRef = null;
+		expression = new ADQLColumn(null, colName);
+		descSorting = desc;
+		position = null;
+	}
+
+	/**
+	 * Updates the current order indication.
+	 *
+	 * @param expr	The expression to order on.
+	 * @param desc	<code>true</code> means DESCending order,
+	 *            	<code>false</code> means ASCending order.
+	 *
+	 * @throws NullPointerException	If the given expression is NULL.
+	 *
+	 * @since 2.0
+	 */
+	public void setOrder(final ADQLOperand expr, boolean desc) throws NullPointerException {
+		if (expr == null)
+			throw new NullPointerException("Impossible to make a reference: the given expression is null!");
+
+		colRef = null;
+		expression = expr;
 		descSorting = desc;
+		position = null;
 	}
 
 	@Override
@@ -155,12 +280,77 @@ public class ADQLOrder extends ColumnReference {
 
 	@Override
 	public String getName() {
-		return super.getName() + (descSorting ? " DESC" : " ASC");
+		return (colRef != null ? colRef.getName() : expression.getName()) + (descSorting ? "_DESC" : "_ASC");
 	}
 
 	@Override
 	public String toADQL() {
-		return super.toADQL() + (descSorting ? " DESC" : " ASC");
+		return (colRef != null ? colRef.toADQL() : expression.toADQL()) + (descSorting ? " DESC" : " ASC");
+	}
+
+	/**
+	 * Gets the position in the original ADQL query string.
+	 *
+	 * @return	The position of this {@link ColumnReference}.
+	 */
+	@Override
+	public final TextPosition getPosition() {
+		return position;
+	}
+
+	/**
+	 * Sets the position at which this {@link ColumnReference} has been found in the original ADQL query string.
+	 *
+	 * @param pos	Position of this {@link ColumnReference}.
+	 */
+	public void setPosition(final TextPosition pos) {
+		position = pos;
+	}
+
+	@Override
+	public final LanguageFeature getFeatureDescription() {
+		return FEATURE;
+	}
+
+	@Override
+	public ADQLIterator adqlIterator() {
+		return new ADQLIterator() {
+
+			private boolean itemDone = false;
+
+			@Override
+			public ADQLObject next() {
+				if (itemDone)
+					throw new NoSuchElementException();
+				else
+					itemDone = true;
+				return (colRef != null ? colRef : expression);
+			}
+
+			@Override
+			public boolean hasNext() {
+				return !itemDone;
+			}
+
+			@Override
+			public void replace(final ADQLObject replacer) throws UnsupportedOperationException, IllegalStateException {
+				if (!itemDone)
+					throw new IllegalStateException("No iteration done yet!");
+				else if (replacer == null)
+					throw new UnsupportedOperationException("Impossible to delete a column reference or an expression from an ORDER BY item! You must delete the complete ORDER BY item.");
+
+				if (replacer instanceof ColumnReference) {
+					colRef = (ColumnReference)replacer;
+					expression = null;
+				} else if (replacer instanceof ADQLOperand) {
+					colRef = null;
+					expression = (ADQLOperand)replacer;
+				} else
+					throw new UnsupportedOperationException("Impossible to replace a column reference or a value expression by a " + replacer.getClass().getName() + "!");
+
+				position = null;
+			}
+		};
 	}
 
 }
diff --git a/src/adql/query/ADQLQuery.java b/src/adql/query/ADQLQuery.java
index 45dbbc9b5735cee8da81934dd733fbf479848e3a..303b75f5714858dec2c04a6f0049444b764640be 100644
--- a/src/adql/query/ADQLQuery.java
+++ b/src/adql/query/ADQLQuery.java
@@ -74,7 +74,7 @@ public class ADQLQuery implements ADQLObject {
 	private ClauseConstraints where;
 
 	/** The ADQL clause GROUP BY. */
-	private ClauseADQL<ADQLColumn> groupBy;
+	private ClauseADQL<ADQLOperand> groupBy;
 
 	/** The ADQL clause HAVING. */
 	private ClauseConstraints having;
@@ -113,7 +113,7 @@ public class ADQLQuery implements ADQLObject {
 		select = new ClauseSelect();
 		from = null;
 		where = new ClauseConstraints("WHERE");
-		groupBy = new ClauseADQL<ADQLColumn>("GROUP BY");
+		groupBy = new ClauseADQL<ADQLOperand>("GROUP BY");
 		having = new ClauseConstraints("HAVING");
 		orderBy = new ClauseADQL<ADQLOrder>("ORDER BY");
 		offset = null;
@@ -132,7 +132,7 @@ public class ADQLQuery implements ADQLObject {
 		select = (ClauseSelect)toCopy.select.getCopy();
 		from = (FromContent)toCopy.from.getCopy();
 		where = (ClauseConstraints)toCopy.where.getCopy();
-		groupBy = (ClauseADQL<ADQLColumn>)toCopy.groupBy.getCopy();
+		groupBy = (ClauseADQL<ADQLOperand>)toCopy.groupBy.getCopy();
 		having = (ClauseConstraints)toCopy.having.getCopy();
 		orderBy = (ClauseADQL<ADQLOrder>)toCopy.orderBy.getCopy();
 		offset = (ClauseOffset)toCopy.offset.getCopy();
@@ -259,7 +259,7 @@ public class ADQLQuery implements ADQLObject {
 	 *
 	 * @return	Its GROUP BY clause.
 	 */
-	public final ClauseADQL<ADQLColumn> getGroupBy() {
+	public final ClauseADQL<ADQLOperand> getGroupBy() {
 		return groupBy;
 	}
 
@@ -274,7 +274,7 @@ public class ADQLQuery implements ADQLObject {
 	 *
 	 * @throws NullPointerException	If the given GROUP BY clause is NULL.
 	 */
-	public void setGroupBy(ClauseADQL<ADQLColumn> newGroupBy) throws NullPointerException {
+	public void setGroupBy(ClauseADQL<ADQLOperand> newGroupBy) throws NullPointerException {
 		if (newGroupBy == null)
 			groupBy.clear();
 		else
@@ -566,7 +566,7 @@ public class ADQLQuery implements ADQLObject {
 							break;
 						case 3:
 							if (replacer instanceof ClauseADQL)
-								groupBy = (ClauseADQL<ADQLColumn>)replacer;
+								groupBy = (ClauseADQL<ADQLOperand>)replacer;
 							else
 								throw new UnsupportedOperationException("Impossible to replace a ClauseADQL (" + groupBy.toADQL() + ") by a " + replacer.getClass().getName() + " (" + replacer.toADQL() + ")!");
 							break;
diff --git a/src/adql/query/ColumnReference.java b/src/adql/query/ColumnReference.java
index 68b3cb4671f6bf2a8d6b40ae6be63ecf99841251..07b21edcfbdacfb61e5d5d3abfec956d259d8b9c 100644
--- a/src/adql/query/ColumnReference.java
+++ b/src/adql/query/ColumnReference.java
@@ -25,10 +25,10 @@ import adql.query.from.ADQLTable;
 import adql.query.operand.ADQLColumn;
 
 /**
- * Represents a reference to a selected column either by an index or by a non-qualified column name/alias.
+ * Represents a reference to a selected column by an index.
  *
  * @author Gr&eacute;gory Mantelet (CDS)
- * @version 2.0 (07/2019)
+ * @version 2.0 (08/2019)
  *
  * @see ADQLOrder
  */
@@ -42,15 +42,16 @@ public class ColumnReference implements ADQLObject {
 	private TextPosition position = null;
 
 	/** Index of a selected column. */
-	private int columnIndex = -1;
+	private int columnIndex;
 
-	/** Name or alias of a selected column. */
-	private String columnName = null;
-
-	/** The corresponding column in the "database". By default, this field is automatically filled by {@link adql.db.DBChecker}. */
+	/** The corresponding column in the "database".
+	 * <p><i>By default, this field is automatically filled by
+	 * {@link adql.db.DBChecker}.</i></p> */
 	private DBColumn dbLink = null;
 
-	/** The {@link ADQLTable} which is supposed to contain this column. By default, this field is automatically filled by {@link adql.db.DBChecker}. */
+	/** The {@link ADQLTable} which is supposed to contain this column.
+	 * <p><i>By default, this field is automatically filled by
+	 * {@link adql.db.DBChecker}.</i></p> */
 	private ADQLTable adqlTable = null;
 
 	/** Indicates whether the column name/alias is case sensitive. */
@@ -59,28 +60,16 @@ public class ColumnReference implements ADQLObject {
 	/**
 	 * Builds a column reference with an index of a selected column.
 	 *
-	 * @param index								Index of a selected column (from 1).
+	 * @param index		Index of a selected column (from 1).
 	 *
-	 * @throws ArrayIndexOutOfBoundsException	If the given index is less or equal 0.
+	 * @throws ArrayIndexOutOfBoundsException	If the given index is less or
+	 *                                       	equal 0.
 	 */
 	public ColumnReference(int index) throws ArrayIndexOutOfBoundsException {
 		if (index <= 0)
 			throw new IndexOutOfBoundsException("Impossible to make a reference to the " + index + "th column: a column index must be greater or equal 1 !");
 
 		columnIndex = index;
-		columnName = null;
-	}
-
-	/**
-	 * Builds a column reference with a name/alias of a selected column.
-	 *
-	 * @param colName					A column name/alias.
-	 *
-	 * @throws NullPointerException 	If the given name is <i>null</i> or is an empty string.
-	 */
-	public ColumnReference(String colName) throws NullPointerException {
-		if (!setColumnName(colName))
-			throw new NullPointerException("Impossible to make a reference: the given column name is null or is an empty string !");
 	}
 
 	/**
@@ -89,7 +78,6 @@ public class ColumnReference implements ADQLObject {
 	 * @param toCopy	The column reference to copy.
 	 */
 	public ColumnReference(ColumnReference toCopy) {
-		columnName = toCopy.columnName;
 		caseSensitive = toCopy.caseSensitive;
 		columnIndex = toCopy.columnIndex;
 	}
@@ -110,7 +98,8 @@ public class ColumnReference implements ADQLObject {
 	}
 
 	/**
-	 * Sets the position at which this {@link ColumnReference} has been found in the original ADQL query string.
+	 * Sets the position at which this {@link ColumnReference} has been found in
+	 * the original ADQL query string.
 	 *
 	 * @param pos	Position of this {@link ColumnReference}.
 	 */
@@ -121,7 +110,7 @@ public class ColumnReference implements ADQLObject {
 	/**
 	 * Gets the index of the referenced column.
 	 *
-	 * @return The index of the referenced column or <i>-1</i> if this column reference has been made with a column name/alias.
+	 * @return	The index of the referenced column.
 	 */
 	public final int getColumnIndex() {
 		return columnIndex;
@@ -131,11 +120,12 @@ public class ColumnReference implements ADQLObject {
 	 * Sets the index of the referenced column.
 	 *
 	 * @param index	The index of the referenced column (must be > 0).
-	 * @return		<i>true</i> if the column referenced has been updated, <i>false</i> otherwise (if index &lt;= 0).
+	 *
+	 * @return	<code>true</code> if the column referenced has been updated,
+	 *        	<code>false</code> otherwise (if index &lt;= 0).
 	 */
 	public final boolean setColumnIndex(int index) {
 		if (index > 0) {
-			columnName = null;
 			columnIndex = index;
 			return true;
 		}
@@ -143,55 +133,11 @@ public class ColumnReference implements ADQLObject {
 	}
 
 	/**
-	 * Tells whether the column is referenced by its index or by its name/alias.
-	 *
-	 * @return	<i>true</i> if by index, <i>false</i> if by name/alias.
-	 */
-	public final boolean isIndex() {
-		return columnName == null;
-	}
-
-	/**
-	 * Gets the name/alias of the referenced column.
-	 *
-	 * @return The referenced column's name/alias or <i>null</i> if this column reference has been made with a column index.
-	 */
-	public final String getColumnName() {
-		return columnName;
-	}
-
-	/**
-	 * Sets the name/alias of the referenced column.
-	 *
-	 * @param name	The referenced column's name/alias (must be different from <i>null</i> and from an empty string).
-	 * @return		<i>true</i> if the column reference has been updated, <i>false</i> otherwise (if name is <i>null</i> or is an empty string).
-	 */
-	public final boolean setColumnName(String name) {
-		if (name == null)
-			return false;
-
-		StringBuffer n = new StringBuffer(name);
-		n.trimToSize();
-		if (n.length() > 1 && n.charAt(0) == '\"' && n.charAt(name.length() - 1) == '\"') {
-			n.deleteCharAt(0);
-			n.deleteCharAt(n.length() - 1);
-			n.trimToSize();
-			if (n.length() > 0)
-				caseSensitive = true;
-		}
-		if (n.length() == 0)
-			return false;
-		else {
-			columnIndex = -1;
-			columnName = n.toString();
-			return true;
-		}
-	}
-
-	/**
-	 * Tells whether the column reference on a column name/alias is case sensitive.
+	 * Tells whether the column reference on a column name/alias is case
+	 * sensitive.
 	 *
-	 * @return	<i>true</i> if the column name/alias is case sensitive, <i>false</i> otherwise.
+	 * @return	<code>true</code> if the column name/alias is case sensitive,
+	 *        	<code>false</code> otherwise.
 	 */
 	public final boolean isCaseSensitive() {
 		return caseSensitive;
@@ -200,7 +146,9 @@ public class ColumnReference implements ADQLObject {
 	/**
 	 * Sets the case sensitivity on the column name/alias.
 	 *
-	 * @param sensitive		<i>true</i> to make case sensitive the column name/alias, <i>false</i> otherwise.
+	 * @param sensitive	<code>true</code> to make case sensitive the column
+	 *                 	name/alias,
+	 *                 	<code>false</code> otherwise.
 	 */
 	public final void setCaseSensitive(boolean sensitive) {
 		caseSensitive = sensitive;
@@ -209,18 +157,25 @@ public class ColumnReference implements ADQLObject {
 	/**
 	 * Gets the corresponding {@link DBColumn}.
 	 *
-	 * @return The corresponding {@link DBColumn} if {@link #getColumnName()} is a column name (not an alias), <i>null</i> otherwise.
+	 * @return	The corresponding {@link DBColumn} if {@link #getColumnName()}
+	 *        	is a column name (not an alias),
+	 *        	or NULL otherwise.
 	 */
 	public final DBColumn getDBLink() {
 		return dbLink;
 	}
 
 	/**
-	 * <p>Sets the {@link DBColumn} corresponding to this {@link ADQLColumn}.</p>
+	 * Sets the {@link DBColumn} corresponding to this {@link ADQLColumn}.
 	 *
-	 * <p>By default, this field is automatically filled by {@link adql.db.DBChecker}.</p>
+	 * <p><i>
+	 * 	By default, this field is automatically filled by
+	 * 	{@link adql.db.DBChecker}.
+	 * </i></p>
 	 *
-	 * @param dbLink Its corresponding {@link DBColumn} if {@link #getColumnName()} is a column name (not an alias), <i>null</i> otherwise.
+	 * @param dbLink	Its corresponding {@link DBColumn} if
+	 *              	{@link #getColumnName()} is a column name (not an alias),
+	 *              	or NULL otherwise.
 	 */
 	public final void setDBLink(DBColumn dbLink) {
 		this.dbLink = dbLink;
@@ -229,18 +184,26 @@ public class ColumnReference implements ADQLObject {
 	/**
 	 * Gets the {@link ADQLTable} from which this column is supposed to come.
 	 *
-	 * @return 	Its source table if {@link #getColumnName()} is a column name (not an alias), otherwise <i>null</i>.
+	 * @return 	Its source table if {@link #getColumnName()} is a column name
+	 *        	(not an alias),
+	 *        	or NULL otherwise.
 	 */
 	public final ADQLTable getAdqlTable() {
 		return adqlTable;
 	}
 
 	/**
-	 * <p>Sets the {@link ADQLTable} from which this column is supposed to come.</p>
+	 * Sets the {@link ADQLTable} from which this column is supposed to come.
 	 *
-	 * <p>By default, this field is automatically filled by {@link adql.db.DBChecker} when {@link adql.db.DBChecker#check(adql.query.ADQLQuery)} is called.</p>
+	 * <p><i>
+	 * 	By default, this field is automatically filled by
+	 * 	{@link adql.db.DBChecker} when
+	 * 	{@link adql.db.DBChecker#check(adql.query.ADQLQuery)} is called.
+	 * </i></p>
 	 *
-	 * @param adqlTable Its source table if {@link #getColumnName()} is a column name (not an alias), <i>null</i> otherwise.
+	 * @param adqlTable	Its source table if {@link #getColumnName()} is a column
+	 *                 	name (not an alias),
+	 *                 	or NULL otherwise.
 	 */
 	public final void setAdqlTable(ADQLTable adqlTable) {
 		this.adqlTable = adqlTable;
@@ -253,7 +216,7 @@ public class ColumnReference implements ADQLObject {
 
 	@Override
 	public String getName() {
-		return isIndex() ? (columnIndex + "") : columnName;
+		return columnIndex + "";
 	}
 
 	@Override
@@ -263,7 +226,7 @@ public class ColumnReference implements ADQLObject {
 
 	@Override
 	public String toADQL() {
-		return isIndex() ? ("" + columnIndex) : (isCaseSensitive() ? ("\"" + columnName + "\"") : columnName);
+		return "" + columnIndex;
 	}
 
 }
diff --git a/src/adql/translator/JDBCTranslator.java b/src/adql/translator/JDBCTranslator.java
index 7042931cbc3f23a49e533446c08fc3a542ca0191..bc482437f23a4b11a5a176f32a2a73fedf73eaca 100644
--- a/src/adql/translator/JDBCTranslator.java
+++ b/src/adql/translator/JDBCTranslator.java
@@ -60,10 +60,10 @@ import adql.query.operand.Operation;
 import adql.query.operand.StringConstant;
 import adql.query.operand.WrappedOperand;
 import adql.query.operand.function.ADQLFunction;
+import adql.query.operand.function.InUnitFunction;
 import adql.query.operand.function.MathFunction;
 import adql.query.operand.function.SQLFunction;
 import adql.query.operand.function.SQLFunctionType;
-import adql.query.operand.function.InUnitFunction;
 import adql.query.operand.function.UserDefinedFunction;
 import adql.query.operand.function.geometry.AreaFunction;
 import adql.query.operand.function.geometry.BoxFunction;
@@ -526,10 +526,7 @@ public abstract class JDBCTranslator implements ADQLTranslator {
 
 	@Override
 	public String translate(ColumnReference ref) throws TranslationException {
-		if (ref instanceof ADQLOrder)
-			return translate((ADQLOrder)ref);
-		else
-			return getDefaultColumnReference(ref);
+		return getDefaultColumnReference(ref);
 	}
 
 	/**
@@ -542,35 +539,15 @@ public abstract class JDBCTranslator implements ADQLTranslator {
 	 * @throws TranslationException If there is an error during the translation.
 	 */
 	protected String getDefaultColumnReference(ColumnReference ref) throws TranslationException {
-		if (ref.isIndex()) {
-			return "" + ref.getColumnIndex();
-		} else {
-			if (ref.getDBLink() == null) {
-				return (ref.isCaseSensitive() ? ("\"" + ref.getColumnName() + "\"") : ref.getColumnName());
-			} else {
-				DBColumn dbCol = ref.getDBLink();
-				StringBuffer colName = new StringBuffer();
-				// Use the table alias if any:
-				if (ref.getAdqlTable() != null && ref.getAdqlTable().hasAlias()) {
-					if (ref.getAdqlTable().isCaseSensitive(IdentifierField.ALIAS))
-						appendIdentifier(colName, ref.getAdqlTable().getAlias(), true).append('.');
-					else
-						appendIdentifier(colName, ref.getAdqlTable().getAlias().toLowerCase(), true).append('.');
-				}
-				// Use the DBTable if any:
-				else if (dbCol.getTable() != null)
-					colName.append(getQualifiedTableName(dbCol.getTable())).append('.');
-
-				appendIdentifier(colName, dbCol.getDBName(), IdentifierField.COLUMN);
-
-				return colName.toString();
-			}
-		}
+		return "" + ref.getColumnIndex();
 	}
 
 	@Override
 	public String translate(ADQLOrder order) throws TranslationException {
-		return getDefaultColumnReference(order) + (order.isDescSorting() ? " DESC" : " ASC");
+		if (order.getColumnReference() != null)
+			return translate(order.getColumnReference()) + (order.isDescSorting() ? " DESC" : " ASC");
+		else
+			return translate(order.getExpression()) + (order.isDescSorting() ? " DESC" : " ASC");
 	}
 
 	/* ************************** */
diff --git a/test/adql/db/TestDBChecker.java b/test/adql/db/TestDBChecker.java
index 04806f49135d147cb4aee07e4cb57eccd310d8f5..391d2320da034bf4a5cbc9f76784b192b3893e1c 100644
--- a/test/adql/db/TestDBChecker.java
+++ b/test/adql/db/TestDBChecker.java
@@ -22,6 +22,7 @@ import adql.db.DBType.DBDatatype;
 import adql.db.FunctionDef.FunctionParam;
 import adql.db.exception.UnresolvedIdentifiersException;
 import adql.parser.ADQLParser;
+import adql.parser.ADQLParser.ADQLVersion;
 import adql.parser.feature.LanguageFeature;
 import adql.parser.grammar.ParseException;
 import adql.query.ADQLObject;
@@ -108,642 +109,656 @@ public class TestDBChecker {
 		 *
 		 * This issue can be tested by creating a ConstraintsGroup (i.e. in a constraints location like WHERE or JOIN...ON,
 		 * a constraint (or more) between parenthesis). */
-		ADQLParser parser = new ADQLParser();
-		parser.setQueryChecker(new DBChecker(tables, new ArrayList<FunctionDef>(0)));
-		try {
-			parser.parseQuery("SELECT * FROM foo WHERE (colI BETWEEN 1 AND 10)");
-		} catch(ParseException pe) {
-			pe.printStackTrace();
-			fail();
+		for(ADQLVersion version : ADQLVersion.values()) {
+			ADQLParser parser = new ADQLParser(version);
+			parser.setQueryChecker(new DBChecker(tables, new ArrayList<FunctionDef>(0)));
+			try {
+				parser.parseQuery("SELECT * FROM foo WHERE (colI BETWEEN 1 AND 10)");
+			} catch(ParseException pe) {
+				pe.printStackTrace();
+				fail("Failed with ADQL-" + version);
+			}
 		}
 	}
 
 	@Test
 	public void testGroupByWithQualifiedColName() {
-		ADQLParser parser = new ADQLParser();
-		parser.setQueryChecker(new DBChecker(tables, new ArrayList<FunctionDef>(0)));
-		try {
-			// Not qualified column name:
-			parser.parseQuery("SELECT colI, COUNT(*) AS cnt FROM foo GROUP BY colI");
-			// Qualified with the table name:
-			parser.parseQuery("SELECT foo.colI, COUNT(*) AS cnt FROM foo GROUP BY foo.colI");
-			// Qualified with the table alias:
-			parser.parseQuery("SELECT f.colI, COUNT(*) AS cnt FROM foo AS f GROUP BY f.colI");
-			// With the SELECT item alias:
-			parser.parseQuery("SELECT colI AS mycol, COUNT(*) AS cnt FROM foo GROUP BY mycol");
-		} catch(ParseException pe) {
-			pe.printStackTrace();
-			fail();
+		for(ADQLVersion version : ADQLVersion.values()) {
+			ADQLParser parser = new ADQLParser(version);
+			parser.setQueryChecker(new DBChecker(tables, new ArrayList<FunctionDef>(0)));
+			try {
+				// Not qualified column name:
+				parser.parseQuery("SELECT colI, COUNT(*) AS cnt FROM foo GROUP BY colI");
+				// Qualified with the table name:
+				parser.parseQuery("SELECT foo.colI, COUNT(*) AS cnt FROM foo GROUP BY foo.colI");
+				// Qualified with the table alias:
+				parser.parseQuery("SELECT f.colI, COUNT(*) AS cnt FROM foo AS f GROUP BY f.colI");
+				// With the SELECT item alias:
+				parser.parseQuery("SELECT colI AS mycol, COUNT(*) AS cnt FROM foo GROUP BY mycol");
+			} catch(ParseException pe) {
+				pe.printStackTrace();
+				fail("Failed with ADQL-" + version);
+			}
 		}
 	}
 
 	@Test
 	public void testQualifiedName() {
-		ADQLParser parser = new ADQLParser();
-		parser.setQueryChecker(new DBChecker(tables, new ArrayList<FunctionDef>(0)));
-		try {
-			// Tests with a table whose the schema is specified:
-			parser.parseQuery("SELECT * FROM foo;");
-			parser.parseQuery("SELECT * FROM aschema.foo;");
-			parser.parseQuery("SELECT foo.* FROM foo;");
-			parser.parseQuery("SELECT aschema.foo.* FROM foo;");
-			parser.parseQuery("SELECT aschema.foo.* FROM aschema.foo;");
-			parser.parseQuery("SELECT \"colS\" FROM foo;");
-			parser.parseQuery("SELECT foo.\"colS\" FROM foo;");
-			parser.parseQuery("SELECT foo.\"colS\" FROM aschema.\"foo\";");
-			parser.parseQuery("SELECT \"aschema\".\"foo\".\"colS\" FROM foo;");
-
-			// Tests with a table without schema:
-			parser.parseQuery("SELECT * FROM foo2;");
-			parser.parseQuery("SELECT foo2.* FROM foo2;");
-			parser.parseQuery("SELECT foo2.* FROM \"foo2\";");
-			parser.parseQuery("SELECT \"foo2\".* FROM \"foo2\";");
-			parser.parseQuery("SELECT oid FROM foo2;");
-			parser.parseQuery("SELECT \"oid\" FROM \"foo2\";");
-			parser.parseQuery("SELECT foo2.oid FROM foo2;");
-			parser.parseQuery("SELECT \"foo2\".\"oid\" FROM \"foo2\";");
-		} catch(ParseException pe) {
-			pe.printStackTrace();
-			fail();
-		}
-
-		// If no schema is specified, then the table is not part of a schema and so, there is no reason a table with a fake schema prefix should work:
-		try {
-			parser.parseQuery("SELECT * FROM noschema.foo2;");
-			fail("The table \"foo2\" has no schema specified and so, is not part of a schema. A fake schema prefix should then be forbidden!");
-		} catch(ParseException pe) {
-		}
-		try {
-			parser.parseQuery("SELECT noschema.foo2.* FROM foo2;");
-			fail("The table \"foo2\" has no schema specified and so, is not part of a schema. A fake schema prefix should then be forbidden!");
-		} catch(ParseException pe) {
+		for(ADQLVersion version : ADQLVersion.values()) {
+			ADQLParser parser = new ADQLParser(version);
+			parser.setQueryChecker(new DBChecker(tables, new ArrayList<FunctionDef>(0)));
+			try {
+				// Tests with a table whose the schema is specified:
+				parser.parseQuery("SELECT * FROM foo;");
+				parser.parseQuery("SELECT * FROM aschema.foo;");
+				parser.parseQuery("SELECT foo.* FROM foo;");
+				parser.parseQuery("SELECT aschema.foo.* FROM foo;");
+				parser.parseQuery("SELECT aschema.foo.* FROM aschema.foo;");
+				parser.parseQuery("SELECT \"colS\" FROM foo;");
+				parser.parseQuery("SELECT foo.\"colS\" FROM foo;");
+				parser.parseQuery("SELECT foo.\"colS\" FROM aschema.\"foo\";");
+				parser.parseQuery("SELECT \"aschema\".\"foo\".\"colS\" FROM foo;");
+
+				// Tests with a table without schema:
+				parser.parseQuery("SELECT * FROM foo2;");
+				parser.parseQuery("SELECT foo2.* FROM foo2;");
+				parser.parseQuery("SELECT foo2.* FROM \"foo2\";");
+				parser.parseQuery("SELECT \"foo2\".* FROM \"foo2\";");
+				parser.parseQuery("SELECT oid FROM foo2;");
+				parser.parseQuery("SELECT \"oid\" FROM \"foo2\";");
+				parser.parseQuery("SELECT foo2.oid FROM foo2;");
+				parser.parseQuery("SELECT \"foo2\".\"oid\" FROM \"foo2\";");
+			} catch(ParseException pe) {
+				pe.printStackTrace();
+				fail();
+			}
+
+			// If no schema is specified, then the table is not part of a schema and so, there is no reason a table with a fake schema prefix should work:
+			try {
+				parser.parseQuery("SELECT * FROM noschema.foo2;");
+				fail("[ADQL-" + version + "] The table \"foo2\" has no schema specified and so, is not part of a schema. A fake schema prefix should then be forbidden!");
+			} catch(ParseException pe) {
+			}
+			try {
+				parser.parseQuery("SELECT noschema.foo2.* FROM foo2;");
+				fail("[ADQL-" + version + "] The table \"foo2\" has no schema specified and so, is not part of a schema. A fake schema prefix should then be forbidden!");
+			} catch(ParseException pe) {
+			}
 		}
 	}
 
 	@Test
 	public void testColRefWithDottedAlias() {
-		ADQLParser parser = new ADQLParser();
-		parser.setQueryChecker(new DBChecker(tables));
-		try {
-			// ORDER BY
-			ADQLQuery adql = parser.parseQuery("SELECT colI AS \"col.I\" FROM aschema.foo ORDER BY \"col.I\"");
-			assertNotNull(adql);
-			assertEquals("SELECT \"aschema\".\"foo\".\"colI\" AS \"col.I\"\nFROM \"aschema\".\"foo\"\nORDER BY \"col.I\" ASC", (new PostgreSQLTranslator()).translate(adql));
-
-			// GROUP BY
-			adql = parser.parseQuery("SELECT colI AS \"col.I\" FROM aschema.foo GROUP BY \"col.I\"");
-			assertNotNull(adql);
-			assertEquals("SELECT \"aschema\".\"foo\".\"colI\" AS \"col.I\"\nFROM \"aschema\".\"foo\"\nGROUP BY \"col.I\"", (new PostgreSQLTranslator()).translate(adql));
-		} catch(ParseException pe) {
-			pe.printStackTrace();
-			fail();
-		} catch(TranslationException te) {
-			te.printStackTrace();
-			fail();
+		for(ADQLVersion version : ADQLVersion.values()) {
+			ADQLParser parser = new ADQLParser(version);
+			parser.setQueryChecker(new DBChecker(tables));
+			try {
+				// ORDER BY
+				ADQLQuery adql = parser.parseQuery("SELECT colI AS \"col.I\" FROM aschema.foo ORDER BY \"col.I\"");
+				assertNotNull(adql);
+				assertEquals("SELECT \"aschema\".\"foo\".\"colI\" AS \"col.I\"\nFROM \"aschema\".\"foo\"\nORDER BY \"col.I\" ASC", (new PostgreSQLTranslator()).translate(adql));
+
+				// GROUP BY
+				adql = parser.parseQuery("SELECT colI AS \"col.I\" FROM aschema.foo GROUP BY \"col.I\"");
+				assertNotNull(adql);
+				assertEquals("SELECT \"aschema\".\"foo\".\"colI\" AS \"col.I\"\nFROM \"aschema\".\"foo\"\nGROUP BY \"col.I\"", (new PostgreSQLTranslator()).translate(adql));
+			} catch(ParseException pe) {
+				pe.printStackTrace();
+				fail("Failed with ADQL-" + version);
+			} catch(TranslationException te) {
+				te.printStackTrace();
+				fail("Failed with ADQL-" + version);
+			}
 		}
 	}
 
 	@Test
 	public void testNumericOrStringValueExpressionPrimary() {
-		ADQLParser parser = new ADQLParser();
-		try {
-			assertNotNull(parser.parseQuery("SELECT 'toto' FROM foo;"));
-			assertNotNull(parser.parseQuery("SELECT ('toto') FROM foo;"));
-			assertNotNull(parser.parseQuery("SELECT (('toto')) FROM foo;"));
-			assertNotNull(parser.parseQuery("SELECT 'toto' || 'blabla' FROM foo;"));
-			assertNotNull(parser.parseQuery("SELECT ('toto' || 'blabla') FROM foo;"));
-			assertNotNull(parser.parseQuery("SELECT (('toto' || 'blabla')) FROM foo;"));
-			assertNotNull(parser.parseQuery("SELECT (('toto') || (('blabla'))) FROM foo;"));
-			assertNotNull(parser.parseQuery("SELECT 3 FROM foo;"));
-			assertNotNull(parser.parseQuery("SELECT ((2+3)*5) FROM foo;"));
-			assertNotNull(parser.parseQuery("SELECT ABS(-123) FROM foo;"));
-			assertNotNull(parser.parseQuery("SELECT ABS(2*-1+5) FROM foo;"));
-			assertNotNull(parser.parseQuery("SELECT ABS(COUNT(*)) FROM foo;"));
-			assertNotNull(parser.parseQuery("SELECT toto FROM foo;"));
-			assertNotNull(parser.parseQuery("SELECT toto * 3 FROM foo;"));
-			assertNotNull(parser.parseQuery("SELECT toto || 'blabla' FROM foo;"));
-			assertNotNull(parser.parseQuery("SELECT 'toto' || 1 FROM foo;"));
-			assertNotNull(parser.parseQuery("SELECT 1 || 'toto' FROM foo;"));
-			assertNotNull(parser.parseQuery("SELECT 'toto' || (-1) FROM foo;"));
-		} catch(ParseException pe) {
-			pe.printStackTrace();
-			fail();
-		}
-		try {
-			parser.parseQuery("SELECT ABS('toto') FROM foo;");
-			fail();
-		} catch(ParseException pe) {
-		}
-		try {
-			parser.parseQuery("SELECT 'toto' || -1 FROM foo;");
-			fail();
-		} catch(ParseException pe) {
-		}
-		try {
-			parser.parseQuery("SELECT -1 || 'toto' FROM foo;");
-			fail();
-		} catch(ParseException pe) {
-		}
-		try {
-			parser.parseQuery("SELECT ABS(('toto' || 'blabla')) FROM foo;");
-			fail();
-		} catch(ParseException pe) {
-		}
-		try {
-			parser.parseQuery("SELECT 'toto' * 3 FROM foo;");
-			fail();
-		} catch(ParseException pe) {
+		for(ADQLVersion version : ADQLVersion.values()) {
+			ADQLParser parser = new ADQLParser(version);
+			try {
+				assertNotNull(parser.parseQuery("SELECT 'toto' FROM foo;"));
+				assertNotNull(parser.parseQuery("SELECT ('toto') FROM foo;"));
+				assertNotNull(parser.parseQuery("SELECT (('toto')) FROM foo;"));
+				assertNotNull(parser.parseQuery("SELECT 'toto' || 'blabla' FROM foo;"));
+				assertNotNull(parser.parseQuery("SELECT ('toto' || 'blabla') FROM foo;"));
+				assertNotNull(parser.parseQuery("SELECT (('toto' || 'blabla')) FROM foo;"));
+				assertNotNull(parser.parseQuery("SELECT (('toto') || (('blabla'))) FROM foo;"));
+				assertNotNull(parser.parseQuery("SELECT 3 FROM foo;"));
+				assertNotNull(parser.parseQuery("SELECT ((2+3)*5) FROM foo;"));
+				assertNotNull(parser.parseQuery("SELECT ABS(-123) FROM foo;"));
+				assertNotNull(parser.parseQuery("SELECT ABS(2*-1+5) FROM foo;"));
+				assertNotNull(parser.parseQuery("SELECT ABS(COUNT(*)) FROM foo;"));
+				assertNotNull(parser.parseQuery("SELECT toto FROM foo;"));
+				assertNotNull(parser.parseQuery("SELECT toto * 3 FROM foo;"));
+				assertNotNull(parser.parseQuery("SELECT toto || 'blabla' FROM foo;"));
+				assertNotNull(parser.parseQuery("SELECT 'toto' || 1 FROM foo;"));
+				assertNotNull(parser.parseQuery("SELECT 1 || 'toto' FROM foo;"));
+				assertNotNull(parser.parseQuery("SELECT 'toto' || (-1) FROM foo;"));
+			} catch(ParseException pe) {
+				pe.printStackTrace();
+				fail("Failed with ADQL-" + version);
+			}
+			try {
+				parser.parseQuery("SELECT ABS('toto') FROM foo;");
+				fail("Failed with ADQL-" + version);
+			} catch(ParseException pe) {
+			}
+			try {
+				parser.parseQuery("SELECT 'toto' || -1 FROM foo;");
+				fail("Failed with ADQL-" + version);
+			} catch(ParseException pe) {
+			}
+			try {
+				parser.parseQuery("SELECT -1 || 'toto' FROM foo;");
+				fail("Failed with ADQL-" + version);
+			} catch(ParseException pe) {
+			}
+			try {
+				parser.parseQuery("SELECT ABS(('toto' || 'blabla')) FROM foo;");
+				fail("Failed with ADQL-" + version);
+			} catch(ParseException pe) {
+			}
+			try {
+				parser.parseQuery("SELECT 'toto' * 3 FROM foo;");
+				fail("Failed with ADQL-" + version);
+			} catch(ParseException pe) {
+			}
 		}
 	}
 
 	@Test
 	public void testUDFManagement() {
-		// UNKNOWN FUNCTIONS ARE NOT ALLOWED:
-		ADQLParser parser = new ADQLParser();
-		parser.getSupportedFeatures().allowAnyUdf(true);
-		parser.setQueryChecker(new DBChecker(tables, new ArrayList<FunctionDef>(0)));
-
-		// Test with a simple ADQL query without unknown or user defined function:
-		try {
-			assertNotNull(parser.parseQuery("SELECT * FROM foo;"));
-		} catch(ParseException e) {
-			e.printStackTrace();
-			fail("A simple and basic query should not be a problem for the parser!");
-		}
-
-		// Test with an ADQL query containing one not declared UDF:
-		try {
-			parser.parseQuery("SELECT toto() FROM foo;");
-			fail("This query contains a UDF while it's not allowed: this test should have failed!");
-		} catch(ParseException e) {
-			assertTrue(e instanceof UnresolvedIdentifiersException);
-			UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e;
-			assertEquals(1, ex.getNbErrors());
-			assertEquals("Unresolved function: \"toto()\"! No UDF has been defined or found with the signature: toto().", ex.getErrors().next().getMessage());
-		}
-
-		// DECLARE THE UDFs:
-		FunctionDef[] udfs;
-		try {
-			udfs = new FunctionDef[]{ new FunctionDef("toto", new DBType(DBDatatype.VARCHAR)), new FunctionDef("tata", new DBType(DBDatatype.INTEGER)) };
-			parser = new ADQLParser();
-			parser.getSupportedFeatures().allowAnyUdf(true);
-			parser.setQueryChecker(new DBChecker(tables, Arrays.asList(udfs)));
-		} catch(ParseException pe) {
-			pe.printStackTrace();
-			fail("Failed initialization because of an invalid UDF declaration! Cause: (cf console)");
-		}
-
-		// Test again:
-		try {
-			assertNotNull(parser.parseQuery("SELECT toto() FROM foo;"));
-			assertNotNull(parser.parseQuery("SELECT tata() FROM foo;"));
-		} catch(ParseException e) {
-			e.printStackTrace();
-			fail("This query contains a DECLARED UDF: this test should have succeeded!");
-		}
-
-		// Test but with at least one parameter:
-		try {
-			parser.parseQuery("SELECT toto('blabla') FROM foo;");
-			fail("This query contains an unknown UDF signature (the fct toto is declared with no parameter): this test should have failed!");
-		} catch(ParseException e) {
-			assertTrue(e instanceof UnresolvedIdentifiersException);
-			UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e;
-			assertEquals(1, ex.getNbErrors());
-			assertEquals("Unresolved function: \"toto('blabla')\"! No UDF has been defined or found with the signature: toto(STRING).", ex.getErrors().next().getMessage());
-		}
-
-		// Test but with at least one column parameter:
-		try {
-			parser.parseQuery("SELECT toto(colS) FROM foo;");
-			fail("This query contains an unknown UDF signature (the fct toto is declared with no parameter): this test should have failed!");
-		} catch(ParseException e) {
-			assertTrue(e instanceof UnresolvedIdentifiersException);
-			UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e;
-			assertEquals(1, ex.getNbErrors());
-			assertEquals("Unresolved function: \"toto(colS)\"! No UDF has been defined or found with the signature: toto(STRING).", ex.getErrors().next().getMessage());
-		}
-
-		// Test but with at least one unknown column parameter:
-		try {
-			parser.parseQuery("SELECT toto(whatami) FROM foo;");
-			fail("This query contains an unknown UDF signature (the fct toto is declared with no parameter): this test should have failed!");
-		} catch(ParseException e) {
-			assertTrue(e instanceof UnresolvedIdentifiersException);
-			UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e;
-			assertEquals(2, ex.getNbErrors());
-			Iterator<ParseException> errors = ex.getErrors();
-			assertEquals("Unknown column \"whatami\" !", errors.next().getMessage());
-			assertEquals("Unresolved function: \"toto(whatami)\"! No UDF has been defined or found with the signature: toto(param1).", errors.next().getMessage());
-		}
-
-		// Test with a UDF whose the class is specified ; the corresponding object in the ADQL tree must be replace by an instance of this class:
-		try {
-			udfs = new FunctionDef[]{ new FunctionDef("toto", new DBType(DBDatatype.VARCHAR), new FunctionParam[]{ new FunctionParam("txt", new DBType(DBDatatype.VARCHAR)) }) };
-			udfs[0].setUDFClass(UDFToto.class);
-			parser = new ADQLParser();
+		for(ADQLVersion version : ADQLVersion.values()) {
+			// UNKNOWN FUNCTIONS ARE NOT ALLOWED:
+			ADQLParser parser = new ADQLParser(version);
 			parser.getSupportedFeatures().allowAnyUdf(true);
-			parser.setQueryChecker(new DBChecker(tables, Arrays.asList(udfs)));
-		} catch(ParseException pe) {
-			pe.printStackTrace();
-			fail("Failed initialization because of an invalid UDF declaration! Cause: (cf console)");
-		}
-
-		try {
-			ADQLQuery query = parser.parseQuery("SELECT toto('blabla') FROM foo;");
-			assertNotNull(query);
-			Iterator<ADQLObject> it = query.search(new SimpleSearchHandler() {
-				@Override
-				protected boolean match(ADQLObject obj) {
-					return (obj instanceof UserDefinedFunction) && ((UserDefinedFunction)obj).getName().equals("toto");
-				}
-			});
-			assertTrue(it.hasNext());
-			assertEquals(UDFToto.class.getName(), it.next().getClass().getName());
-			assertFalse(it.hasNext());
-		} catch(Exception e) {
-			e.printStackTrace();
-			fail("This query contains a DECLARED UDF with a valid UserDefinedFunction class: this test should have succeeded!");
-		}
-
-		// Test with a wrong parameter type:
-		try {
-			parser.parseQuery("SELECT toto(123) FROM foo;");
-			fail("This query contains an unknown UDF signature (the fct toto is declared with one parameter of type STRING...here it is a numeric): this test should have failed!");
-		} catch(Exception e) {
-			assertTrue(e instanceof UnresolvedIdentifiersException);
-			UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e;
-			assertEquals(1, ex.getNbErrors());
-			assertEquals("Unresolved function: \"toto(123)\"! No UDF has been defined or found with the signature: toto(NUMERIC).", ex.getErrors().next().getMessage());
-		}
-
-		// Test with UDF class constructor throwing an exception:
-		try {
-			udfs = new FunctionDef[]{ new FunctionDef("toto", new DBType(DBDatatype.VARCHAR), new FunctionParam[]{ new FunctionParam("txt", new DBType(DBDatatype.VARCHAR)) }) };
-			udfs[0].setUDFClass(WrongUDFToto.class);
-			parser = new ADQLParser();
-			parser.getSupportedFeatures().allowAnyUdf(true);
-			parser.setQueryChecker(new DBChecker(tables, Arrays.asList(udfs)));
-		} catch(ParseException pe) {
-			pe.printStackTrace();
-			fail("Failed initialization because of an invalid UDF declaration! Cause: (cf console)");
-		}
-
-		try {
-			parser.parseQuery("SELECT toto('blabla') FROM foo;");
-			fail("The set UDF class constructor has throw an error: this test should have failed!");
-		} catch(Exception e) {
-			assertTrue(e instanceof UnresolvedIdentifiersException);
-			UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e;
-			assertEquals(1, ex.getNbErrors());
-			assertEquals("Impossible to represent the function \"toto\": the following error occured while creating this representation: \"[Exception] Systematic error!\"", ex.getErrors().next().getMessage());
+			parser.setQueryChecker(new DBChecker(tables, new ArrayList<FunctionDef>(0)));
+
+			// Test with a simple ADQL query without unknown or user defined function:
+			try {
+				assertNotNull(parser.parseQuery("SELECT * FROM foo;"));
+			} catch(ParseException e) {
+				e.printStackTrace();
+				fail("A simple and basic query should not be a problem for the parser!");
+			}
+
+			// Test with an ADQL query containing one not declared UDF:
+			try {
+				parser.parseQuery("SELECT toto() FROM foo;");
+				fail("This query contains a UDF while it's not allowed: this test should have failed!");
+			} catch(ParseException e) {
+				assertTrue(e instanceof UnresolvedIdentifiersException);
+				UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e;
+				assertEquals(1, ex.getNbErrors());
+				assertEquals("Unresolved function: \"toto()\"! No UDF has been defined or found with the signature: toto().", ex.getErrors().next().getMessage());
+			}
+
+			// DECLARE THE UDFs:
+			FunctionDef[] udfs;
+			try {
+				udfs = new FunctionDef[]{ new FunctionDef("toto", new DBType(DBDatatype.VARCHAR)), new FunctionDef("tata", new DBType(DBDatatype.INTEGER)) };
+				parser = new ADQLParser(version);
+				parser.getSupportedFeatures().allowAnyUdf(true);
+				parser.setQueryChecker(new DBChecker(tables, Arrays.asList(udfs)));
+			} catch(ParseException pe) {
+				pe.printStackTrace();
+				fail("Failed initialization because of an invalid UDF declaration! Cause: (cf console)");
+			}
+
+			// Test again:
+			try {
+				assertNotNull(parser.parseQuery("SELECT toto() FROM foo;"));
+				assertNotNull(parser.parseQuery("SELECT tata() FROM foo;"));
+			} catch(ParseException e) {
+				e.printStackTrace();
+				fail("This query contains a DECLARED UDF: this test should have succeeded!");
+			}
+
+			// Test but with at least one parameter:
+			try {
+				parser.parseQuery("SELECT toto('blabla') FROM foo;");
+				fail("This query contains an unknown UDF signature (the fct toto is declared with no parameter): this test should have failed!");
+			} catch(ParseException e) {
+				assertTrue(e instanceof UnresolvedIdentifiersException);
+				UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e;
+				assertEquals(1, ex.getNbErrors());
+				assertEquals("Unresolved function: \"toto('blabla')\"! No UDF has been defined or found with the signature: toto(STRING).", ex.getErrors().next().getMessage());
+			}
+
+			// Test but with at least one column parameter:
+			try {
+				parser.parseQuery("SELECT toto(colS) FROM foo;");
+				fail("This query contains an unknown UDF signature (the fct toto is declared with no parameter): this test should have failed!");
+			} catch(ParseException e) {
+				assertTrue(e instanceof UnresolvedIdentifiersException);
+				UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e;
+				assertEquals(1, ex.getNbErrors());
+				assertEquals("Unresolved function: \"toto(colS)\"! No UDF has been defined or found with the signature: toto(STRING).", ex.getErrors().next().getMessage());
+			}
+
+			// Test but with at least one unknown column parameter:
+			try {
+				parser.parseQuery("SELECT toto(whatami) FROM foo;");
+				fail("This query contains an unknown UDF signature (the fct toto is declared with no parameter): this test should have failed!");
+			} catch(ParseException e) {
+				assertTrue(e instanceof UnresolvedIdentifiersException);
+				UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e;
+				assertEquals(2, ex.getNbErrors());
+				Iterator<ParseException> errors = ex.getErrors();
+				assertEquals("Unknown column \"whatami\" !", errors.next().getMessage());
+				assertEquals("Unresolved function: \"toto(whatami)\"! No UDF has been defined or found with the signature: toto(param1).", errors.next().getMessage());
+			}
+
+			// Test with a UDF whose the class is specified ; the corresponding object in the ADQL tree must be replace by an instance of this class:
+			try {
+				udfs = new FunctionDef[]{ new FunctionDef("toto", new DBType(DBDatatype.VARCHAR), new FunctionParam[]{ new FunctionParam("txt", new DBType(DBDatatype.VARCHAR)) }) };
+				udfs[0].setUDFClass(UDFToto.class);
+				parser = new ADQLParser(version);
+				parser.getSupportedFeatures().allowAnyUdf(true);
+				parser.setQueryChecker(new DBChecker(tables, Arrays.asList(udfs)));
+			} catch(ParseException pe) {
+				pe.printStackTrace();
+				fail("Failed initialization because of an invalid UDF declaration! Cause: (cf console)");
+			}
+
+			try {
+				ADQLQuery query = parser.parseQuery("SELECT toto('blabla') FROM foo;");
+				assertNotNull(query);
+				Iterator<ADQLObject> it = query.search(new SimpleSearchHandler() {
+					@Override
+					protected boolean match(ADQLObject obj) {
+						return (obj instanceof UserDefinedFunction) && ((UserDefinedFunction)obj).getName().equals("toto");
+					}
+				});
+				assertTrue(it.hasNext());
+				assertEquals(UDFToto.class.getName(), it.next().getClass().getName());
+				assertFalse(it.hasNext());
+			} catch(Exception e) {
+				e.printStackTrace();
+				fail("This query contains a DECLARED UDF with a valid UserDefinedFunction class: this test should have succeeded!");
+			}
+
+			// Test with a wrong parameter type:
+			try {
+				parser.parseQuery("SELECT toto(123) FROM foo;");
+				fail("This query contains an unknown UDF signature (the fct toto is declared with one parameter of type STRING...here it is a numeric): this test should have failed!");
+			} catch(Exception e) {
+				assertTrue(e instanceof UnresolvedIdentifiersException);
+				UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e;
+				assertEquals(1, ex.getNbErrors());
+				assertEquals("Unresolved function: \"toto(123)\"! No UDF has been defined or found with the signature: toto(NUMERIC).", ex.getErrors().next().getMessage());
+			}
+
+			// Test with UDF class constructor throwing an exception:
+			try {
+				udfs = new FunctionDef[]{ new FunctionDef("toto", new DBType(DBDatatype.VARCHAR), new FunctionParam[]{ new FunctionParam("txt", new DBType(DBDatatype.VARCHAR)) }) };
+				udfs[0].setUDFClass(WrongUDFToto.class);
+				parser = new ADQLParser(version);
+				parser.getSupportedFeatures().allowAnyUdf(true);
+				parser.setQueryChecker(new DBChecker(tables, Arrays.asList(udfs)));
+			} catch(ParseException pe) {
+				pe.printStackTrace();
+				fail("Failed initialization because of an invalid UDF declaration! Cause: (cf console)");
+			}
+
+			try {
+				parser.parseQuery("SELECT toto('blabla') FROM foo;");
+				fail("The set UDF class constructor has throw an error: this test should have failed!");
+			} catch(Exception e) {
+				assertTrue(e instanceof UnresolvedIdentifiersException);
+				UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e;
+				assertEquals(1, ex.getNbErrors());
+				assertEquals("Impossible to represent the function \"toto\": the following error occured while creating this representation: \"[Exception] Systematic error!\"", ex.getErrors().next().getMessage());
+			}
 		}
 	}
 
 	@Test
 	public void testTypesChecking() {
-		// DECLARE A SIMPLE PARSER:
-		ADQLParser parser = new ADQLParser();
-		parser.setQueryChecker(new DBChecker(tables));
-
-		// Test the type of columns generated by the parser:
-		try {
-			ADQLQuery query = parser.parseQuery("SELECT colS, colI, colG FROM foo;");
-			ADQLOperand colS = query.getSelect().get(0).getOperand();
-			ADQLOperand colI = query.getSelect().get(1).getOperand();
-			ADQLOperand colG = query.getSelect().get(2).getOperand();
-			// test string column:
-			assertTrue(colS instanceof ADQLColumn);
-			assertTrue(colS.isString());
-			assertFalse(colS.isNumeric());
-			assertFalse(colS.isGeometry());
-			// test integer column:
-			assertTrue(colI instanceof ADQLColumn);
-			assertFalse(colI.isString());
-			assertTrue(colI.isNumeric());
-			assertFalse(colI.isGeometry());
-			// test geometry column:
-			assertTrue(colG instanceof ADQLColumn);
-			assertFalse(colG.isString());
-			assertFalse(colG.isNumeric());
-			assertTrue(colG.isGeometry());
-		} catch(ParseException e1) {
-			if (e1 instanceof UnresolvedIdentifiersException)
-				((UnresolvedIdentifiersException)e1).getErrors().next().printStackTrace();
-			else
+		for(ADQLVersion version : ADQLVersion.values()) {
+			// DECLARE A SIMPLE PARSER:
+			ADQLParser parser = new ADQLParser(version);
+			parser.setQueryChecker(new DBChecker(tables));
+
+			// Test the type of columns generated by the parser:
+			try {
+				ADQLQuery query = parser.parseQuery("SELECT colS, colI, colG FROM foo;");
+				ADQLOperand colS = query.getSelect().get(0).getOperand();
+				ADQLOperand colI = query.getSelect().get(1).getOperand();
+				ADQLOperand colG = query.getSelect().get(2).getOperand();
+				// test string column:
+				assertTrue(colS instanceof ADQLColumn);
+				assertTrue(colS.isString());
+				assertFalse(colS.isNumeric());
+				assertFalse(colS.isGeometry());
+				// test integer column:
+				assertTrue(colI instanceof ADQLColumn);
+				assertFalse(colI.isString());
+				assertTrue(colI.isNumeric());
+				assertFalse(colI.isGeometry());
+				// test geometry column:
+				assertTrue(colG instanceof ADQLColumn);
+				assertFalse(colG.isString());
+				assertFalse(colG.isNumeric());
+				assertTrue(colG.isGeometry());
+			} catch(ParseException e1) {
+				if (e1 instanceof UnresolvedIdentifiersException)
+					((UnresolvedIdentifiersException)e1).getErrors().next().printStackTrace();
+				else
+					e1.printStackTrace();
+				fail("This query contains known columns: this test should have succeeded!");
+			}
+
+			// Test the expected type - NUMERIC - generated by the parser:
+			try {
+				assertNotNull(parser.parseQuery("SELECT colI * 3 FROM foo;"));
+			} catch(ParseException e) {
+				e.printStackTrace();
+				fail("This query contains a product between 2 numerics: this test should have succeeded!");
+			}
+			try {
+				parser.parseQuery("SELECT colS * 3 FROM foo;");
+				fail("This query contains a product between a string and an integer: this test should have failed!");
+			} catch(ParseException e) {
+				assertTrue(e instanceof UnresolvedIdentifiersException);
+				UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e;
+				assertEquals(1, ex.getNbErrors());
+				assertEquals("Type mismatch! A numeric value was expected instead of \"colS\".", ex.getErrors().next().getMessage());
+			}
+			try {
+				parser.parseQuery("SELECT colG * 3 FROM foo;");
+				fail("This query contains a product between a geometry and an integer: this test should have failed!");
+			} catch(ParseException e) {
+				assertTrue(e instanceof UnresolvedIdentifiersException);
+				UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e;
+				assertEquals(1, ex.getNbErrors());
+				assertEquals("Type mismatch! A numeric value was expected instead of \"colG\".", ex.getErrors().next().getMessage());
+			}
+
+			// Test the expected type - STRING - generated by the parser:
+			try {
+				assertNotNull(parser.parseQuery("SELECT colS || 'blabla' FROM foo;"));
+			} catch(ParseException e) {
+				e.printStackTrace();
+				fail("This query contains a concatenation between 2 strings: this test should have succeeded!");
+			}
+			try {
+				assertNotNull(parser.parseQuery("SELECT colI || 'blabla' FROM foo;"));
+			} catch(ParseException e) {
+				e.printStackTrace();
+				fail("This query contains a concatenation between a column (whatever its type) and a string: this test should have succeeded!");
+			}
+			try {
+				assertNotNull(parser.parseQuery("SELECT colG || 'blabla' FROM foo;"));
+			} catch(ParseException e) {
+				e.printStackTrace();
+				fail("This query contains a concatenation between a column (whatever its type) and a string: this test should have succeeded!");
+			}
+
+			// Test the expected type - GEOMETRY - generated by the parser:
+			try {
+				assertNotNull(parser.parseQuery("SELECT CONTAINS(colG, CIRCLE('', 1, 2, 5)) FROM foo;"));
+			} catch(ParseException e) {
+				e.printStackTrace();
+				fail("This query contains a geometrical predicate between 2 geometries: this test should have succeeded!");
+			}
+			try {
+				parser.parseQuery("SELECT CONTAINS(colI, CIRCLE('', 1, 2, 5)) FROM foo;");
+				fail("This query contains a geometrical predicate between an integer and a geometry: this test should have failed!");
+			} catch(ParseException e) {
+				assertTrue(e instanceof UnresolvedIdentifiersException);
+				UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e;
+				assertEquals(1, ex.getNbErrors());
+				assertEquals("Type mismatch! A geometry was expected instead of \"colI\".", ex.getErrors().next().getMessage());
+			}
+			try {
+				parser.parseQuery("SELECT CONTAINS(colS, CIRCLE('', 1, 2, 5)) FROM foo;");
+				fail("This query contains a geometrical predicate between a string and a geometry: this test should have failed!");
+			} catch(ParseException e) {
+				assertTrue(e instanceof UnresolvedIdentifiersException);
+				UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e;
+				assertEquals(1, ex.getNbErrors());
+				assertEquals("Type mismatch! A geometry was expected instead of \"colS\".", ex.getErrors().next().getMessage());
+			}
+
+			// DECLARE SOME UDFs:
+			FunctionDef[] udfs;
+			try {
+				udfs = new FunctionDef[]{ new FunctionDef("toto", new DBType(DBDatatype.VARCHAR)), new FunctionDef("tata", new DBType(DBDatatype.INTEGER)), new FunctionDef("titi", new DBType(DBDatatype.REGION)) };
+				parser = new ADQLParser(version);
+				parser.setQueryChecker(new DBChecker(tables, Arrays.asList(udfs)));
+			} catch(ParseException pe) {
+				pe.printStackTrace();
+				fail("Failed initialization because of an invalid UDF declaration! Cause: (cf console)");
+			}
+
+			// Test the return type of the function TOTO generated by the parser:
+			try {
+				ADQLQuery query = parser.parseQuery("SELECT toto() FROM foo;");
+				ADQLOperand fct = query.getSelect().get(0).getOperand();
+				assertTrue(fct instanceof DefaultUDF);
+				assertNotNull(((DefaultUDF)fct).getDefinition());
+				assertTrue(fct.isString());
+				assertFalse(fct.isNumeric());
+				assertFalse(fct.isGeometry());
+			} catch(ParseException e1) {
 				e1.printStackTrace();
-			fail("This query contains known columns: this test should have succeeded!");
-		}
-
-		// Test the expected type - NUMERIC - generated by the parser:
-		try {
-			assertNotNull(parser.parseQuery("SELECT colI * 3 FROM foo;"));
-		} catch(ParseException e) {
-			e.printStackTrace();
-			fail("This query contains a product between 2 numerics: this test should have succeeded!");
-		}
-		try {
-			parser.parseQuery("SELECT colS * 3 FROM foo;");
-			fail("This query contains a product between a string and an integer: this test should have failed!");
-		} catch(ParseException e) {
-			assertTrue(e instanceof UnresolvedIdentifiersException);
-			UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e;
-			assertEquals(1, ex.getNbErrors());
-			assertEquals("Type mismatch! A numeric value was expected instead of \"colS\".", ex.getErrors().next().getMessage());
-		}
-		try {
-			parser.parseQuery("SELECT colG * 3 FROM foo;");
-			fail("This query contains a product between a geometry and an integer: this test should have failed!");
-		} catch(ParseException e) {
-			assertTrue(e instanceof UnresolvedIdentifiersException);
-			UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e;
-			assertEquals(1, ex.getNbErrors());
-			assertEquals("Type mismatch! A numeric value was expected instead of \"colG\".", ex.getErrors().next().getMessage());
-		}
-
-		// Test the expected type - STRING - generated by the parser:
-		try {
-			assertNotNull(parser.parseQuery("SELECT colS || 'blabla' FROM foo;"));
-		} catch(ParseException e) {
-			e.printStackTrace();
-			fail("This query contains a concatenation between 2 strings: this test should have succeeded!");
-		}
-		try {
-			assertNotNull(parser.parseQuery("SELECT colI || 'blabla' FROM foo;"));
-		} catch(ParseException e) {
-			e.printStackTrace();
-			fail("This query contains a concatenation between a column (whatever its type) and a string: this test should have succeeded!");
-		}
-		try {
-			assertNotNull(parser.parseQuery("SELECT colG || 'blabla' FROM foo;"));
-		} catch(ParseException e) {
-			e.printStackTrace();
-			fail("This query contains a concatenation between a column (whatever its type) and a string: this test should have succeeded!");
-		}
-
-		// Test the expected type - GEOMETRY - generated by the parser:
-		try {
-			assertNotNull(parser.parseQuery("SELECT CONTAINS(colG, CIRCLE('', 1, 2, 5)) FROM foo;"));
-		} catch(ParseException e) {
-			e.printStackTrace();
-			fail("This query contains a geometrical predicate between 2 geometries: this test should have succeeded!");
-		}
-		try {
-			parser.parseQuery("SELECT CONTAINS(colI, CIRCLE('', 1, 2, 5)) FROM foo;");
-			fail("This query contains a geometrical predicate between an integer and a geometry: this test should have failed!");
-		} catch(ParseException e) {
-			assertTrue(e instanceof UnresolvedIdentifiersException);
-			UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e;
-			assertEquals(1, ex.getNbErrors());
-			assertEquals("Type mismatch! A geometry was expected instead of \"colI\".", ex.getErrors().next().getMessage());
-		}
-		try {
-			parser.parseQuery("SELECT CONTAINS(colS, CIRCLE('', 1, 2, 5)) FROM foo;");
-			fail("This query contains a geometrical predicate between a string and a geometry: this test should have failed!");
-		} catch(ParseException e) {
-			assertTrue(e instanceof UnresolvedIdentifiersException);
-			UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e;
-			assertEquals(1, ex.getNbErrors());
-			assertEquals("Type mismatch! A geometry was expected instead of \"colS\".", ex.getErrors().next().getMessage());
-		}
-
-		// DECLARE SOME UDFs:
-		FunctionDef[] udfs;
-		try {
-			udfs = new FunctionDef[]{ new FunctionDef("toto", new DBType(DBDatatype.VARCHAR)), new FunctionDef("tata", new DBType(DBDatatype.INTEGER)), new FunctionDef("titi", new DBType(DBDatatype.REGION)) };
-			parser = new ADQLParser();
-			parser.setQueryChecker(new DBChecker(tables, Arrays.asList(udfs)));
-		} catch(ParseException pe) {
-			pe.printStackTrace();
-			fail("Failed initialization because of an invalid UDF declaration! Cause: (cf console)");
-		}
-
-		// Test the return type of the function TOTO generated by the parser:
-		try {
-			ADQLQuery query = parser.parseQuery("SELECT toto() FROM foo;");
-			ADQLOperand fct = query.getSelect().get(0).getOperand();
-			assertTrue(fct instanceof DefaultUDF);
-			assertNotNull(((DefaultUDF)fct).getDefinition());
-			assertTrue(fct.isString());
-			assertFalse(fct.isNumeric());
-			assertFalse(fct.isGeometry());
-		} catch(ParseException e1) {
-			e1.printStackTrace();
-			fail("This query contains a DECLARED UDF: this test should have succeeded!");
-		}
-
-		// Test the return type checking inside a whole query:
-		try {
-			assertNotNull(parser.parseQuery("SELECT toto() || 'Blabla ' AS \"SuperText\" FROM foo;"));
-		} catch(ParseException e1) {
-			e1.printStackTrace();
-			fail("This query contains a DECLARED UDF concatenated to a String: this test should have succeeded!");
-		}
-		try {
-			parser.parseQuery("SELECT toto()*3 AS \"SuperError\" FROM foo;");
-			fail("This query contains a DECLARED UDF BUT used as numeric...which is here not possible: this test should have failed!");
-		} catch(ParseException e1) {
-			assertTrue(e1 instanceof UnresolvedIdentifiersException);
-			UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e1;
-			assertEquals(1, ex.getNbErrors());
-			assertEquals("Type mismatch! A numeric value was expected instead of \"toto()\".", ex.getErrors().next().getMessage());
-		}
-
-		// Test the return type of the function TATA generated by the parser:
-		try {
-			ADQLQuery query = parser.parseQuery("SELECT tata() FROM foo;");
-			ADQLOperand fct = query.getSelect().get(0).getOperand();
-			assertTrue(fct instanceof DefaultUDF);
-			assertNotNull(((DefaultUDF)fct).getDefinition());
-			assertFalse(fct.isString());
-			assertTrue(fct.isNumeric());
-			assertFalse(fct.isGeometry());
-		} catch(ParseException e1) {
-			e1.printStackTrace();
-			fail("This query contains a DECLARED UDF: this test should have succeeded!");
-		}
-
-		// Test the return type checking inside a whole query:
-		try {
-			assertNotNull(parser.parseQuery("SELECT tata()*3 AS \"aNumeric\" FROM foo;"));
-		} catch(ParseException e1) {
-			e1.printStackTrace();
-			fail("This query contains a DECLARED UDF multiplicated by 3: this test should have succeeded!");
-		}
-		try {
-			parser.parseQuery("SELECT 'Blabla ' || tata() AS \"SuperError\" FROM foo;");
-			fail("This query contains a DECLARED UDF BUT used as string...which is here not possible: this test should have failed!");
-		} catch(ParseException e1) {
-			assertTrue(e1 instanceof UnresolvedIdentifiersException);
-			UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e1;
-			assertEquals(1, ex.getNbErrors());
-			assertEquals("Type mismatch! A string value was expected instead of \"tata()\".", ex.getErrors().next().getMessage());
-		}
-		try {
-			parser.parseQuery("SELECT tata() || 'Blabla ' AS \"SuperError\" FROM foo;");
-			fail("This query contains a DECLARED UDF BUT used as string...which is here not possible: this test should have failed!");
-		} catch(ParseException e1) {
-			assertTrue(e1 instanceof UnresolvedIdentifiersException);
-			UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e1;
-			assertEquals(1, ex.getNbErrors());
-			assertEquals("Type mismatch! A string value was expected instead of \"tata()\".", ex.getErrors().next().getMessage());
-		}
-
-		// Test the return type of the function TITI generated by the parser:
-		try {
-			ADQLQuery query = parser.parseQuery("SELECT titi() FROM foo;");
-			ADQLOperand fct = query.getSelect().get(0).getOperand();
-			assertTrue(fct instanceof DefaultUDF);
-			assertNotNull(((DefaultUDF)fct).getDefinition());
-			assertFalse(fct.isString());
-			assertFalse(fct.isNumeric());
-			assertTrue(fct.isGeometry());
-		} catch(ParseException e1) {
-			e1.printStackTrace();
-			fail("This query contains a DECLARED UDF: this test should have succeeded!");
-		}
-
-		// Test the return type checking inside a whole query:
-		try {
-			parser.parseQuery("SELECT CONTAINS(colG, titi()) ' AS \"Super\" FROM foo;");
-			fail("Geometrical UDFs are not allowed for the moment in the ADQL language: this test should have failed!");
-		} catch(ParseException e1) {
-			assertTrue(e1 instanceof ParseException);
-			assertEquals(" Encountered \"(\". Was expecting one of: \")\" \".\" \".\" \")\" ", e1.getMessage());
-		}
-		try {
-			parser.parseQuery("SELECT titi()*3 AS \"SuperError\" FROM foo;");
-			fail("This query contains a DECLARED UDF BUT used as numeric...which is here not possible: this test should have failed!");
-		} catch(ParseException e1) {
-			assertTrue(e1 instanceof UnresolvedIdentifiersException);
-			UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e1;
-			assertEquals(1, ex.getNbErrors());
-			assertEquals("Type mismatch! A numeric value was expected instead of \"titi()\".", ex.getErrors().next().getMessage());
-		}
-
-		// Try with functions wrapped on 2 levels:
-		// i.e. fct1('blabla', fct2(fct3('blabla')))
-		try {
-			FunctionDef[] complexFcts = new FunctionDef[3];
-			complexFcts[0] = new FunctionDef("fct1", new DBType(DBDatatype.VARCHAR), new FunctionParam[]{ new FunctionParam("str", new DBType(DBDatatype.VARCHAR)), new FunctionParam("num", new DBType(DBDatatype.INTEGER)) });
-			complexFcts[1] = new FunctionDef("fct2", new DBType(DBDatatype.INTEGER), new FunctionParam[]{ new FunctionParam("str", new DBType(DBDatatype.VARCHAR)) });
-			complexFcts[2] = new FunctionDef("fct3", new DBType(DBDatatype.VARCHAR), new FunctionParam[]{ new FunctionParam("str", new DBType(DBDatatype.VARCHAR)) });
-			parser = new ADQLParser();
-			parser.setQueryChecker(new DBChecker(tables, Arrays.asList(complexFcts)));
-		} catch(ParseException pe) {
-			pe.printStackTrace();
-			fail("Failed initialization because of an invalid UDF declaration! Cause: (cf console)");
-		}
-
-		// With parameters of the good type:
-		try {
-			assertNotNull(parser.parseQuery("SELECT fct1('blabla', fct2(fct3('blabla'))) FROM foo"));
-		} catch(ParseException pe) {
-			pe.printStackTrace();
-			fail("Types are matching: this test should have succeeded!");
-		}
-		// With parameters of the bad type:
-		try {
-			parser.parseQuery("SELECT fct2(fct1('blabla', fct3('blabla'))) FROM foo");
-			fail("Parameters types are not matching: the parsing should have failed!");
-		} catch(ParseException pe) {
-			assertEquals(UnresolvedIdentifiersException.class, pe.getClass());
-			assertEquals(1, ((UnresolvedIdentifiersException)pe).getNbErrors());
-			ParseException innerPe = ((UnresolvedIdentifiersException)pe).getErrors().next();
-			assertEquals("Unresolved function: \"fct1('blabla', fct3('blabla'))\"! No UDF has been defined or found with the signature: fct1(STRING, STRING).", innerPe.getMessage());
-		}
-
-		// CLEAR ALL UDFs AND ALLOW UNKNOWN FUNCTION:
-		parser = new ADQLParser();
-		parser.setQueryChecker(new DBChecker(tables, null));
-
-		// Test again:
-		try {
-			assertNotNull(parser.parseQuery("SELECT toto() FROM foo;"));
-		} catch(ParseException e) {
-			e.printStackTrace();
-			fail("The parser allow ANY unknown function: this test should have succeeded!");
-		}
-
-		// Test the return type of the function generated by the parser:
-		try {
-			ADQLQuery query = parser.parseQuery("SELECT toto() FROM foo;");
-			ADQLOperand fct = query.getSelect().get(0).getOperand();
-			assertTrue(fct instanceof DefaultUDF);
-			assertNull(((DefaultUDF)fct).getDefinition());
-			assertTrue(fct.isString());
-			assertTrue(fct.isNumeric());
-		} catch(ParseException e1) {
-			e1.printStackTrace();
-			fail("The parser allow ANY unknown function: this test should have succeeded!");
-		}
-
-		// DECLARE THE UDF (while unknown functions are allowed):
-		try {
-			parser = new ADQLParser();
-			parser.setQueryChecker(new DBChecker(tables, Arrays.asList(new FunctionDef[]{ new FunctionDef("toto", new DBType(DBDatatype.VARCHAR)) })));
-		} catch(ParseException pe) {
-			pe.printStackTrace();
-			fail("Failed initialization because of an invalid UDF declaration! Cause: (cf console)");
-		}
-
-		// Test the return type of the function generated by the parser:
-		try {
-			ADQLQuery query = parser.parseQuery("SELECT toto() FROM foo;");
-			ADQLOperand fct = query.getSelect().get(0).getOperand();
-			assertTrue(fct instanceof DefaultUDF);
-			assertNotNull(((DefaultUDF)fct).getDefinition());
-			assertTrue(fct.isString());
-			assertFalse(fct.isNumeric());
-		} catch(ParseException e1) {
-			e1.printStackTrace();
-			fail("The parser allow ANY unknown function: this test should have succeeded!");
-		}
+				fail("This query contains a DECLARED UDF: this test should have succeeded!");
+			}
 
-		// DECLARE UDFs WITH SAME NAMES BUT DIFFERENT TYPE OF ARGUMENT:
-		try {
-			udfs = new FunctionDef[]{ new FunctionDef("toto", new DBType(DBDatatype.VARCHAR), new FunctionParam[]{ new FunctionParam("attr", new DBType(DBDatatype.VARCHAR)) }), new FunctionDef("toto", new DBType(DBDatatype.INTEGER), new FunctionParam[]{ new FunctionParam("attr", new DBType(DBDatatype.INTEGER)) }), new FunctionDef("toto", new DBType(DBDatatype.INTEGER), new FunctionParam[]{ new FunctionParam("attr", new DBType(DBDatatype.POINT)) }) };
-			parser = new ADQLParser();
-			parser.setQueryChecker(new DBChecker(tables, Arrays.asList(udfs)));
-		} catch(ParseException pe) {
-			pe.printStackTrace();
-			fail("Failed initialization because of an invalid UDF declaration! Cause: (cf console)");
-		}
+			// Test the return type checking inside a whole query:
+			try {
+				assertNotNull(parser.parseQuery("SELECT toto() || 'Blabla ' AS \"SuperText\" FROM foo;"));
+			} catch(ParseException e1) {
+				e1.printStackTrace();
+				fail("This query contains a DECLARED UDF concatenated to a String: this test should have succeeded!");
+			}
+			try {
+				parser.parseQuery("SELECT toto()*3 AS \"SuperError\" FROM foo;");
+				fail("This query contains a DECLARED UDF BUT used as numeric...which is here not possible: this test should have failed!");
+			} catch(ParseException e1) {
+				assertTrue(e1 instanceof UnresolvedIdentifiersException);
+				UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e1;
+				assertEquals(1, ex.getNbErrors());
+				assertEquals("Type mismatch! A numeric value was expected instead of \"toto()\".", ex.getErrors().next().getMessage());
+			}
+
+			// Test the return type of the function TATA generated by the parser:
+			try {
+				ADQLQuery query = parser.parseQuery("SELECT tata() FROM foo;");
+				ADQLOperand fct = query.getSelect().get(0).getOperand();
+				assertTrue(fct instanceof DefaultUDF);
+				assertNotNull(((DefaultUDF)fct).getDefinition());
+				assertFalse(fct.isString());
+				assertTrue(fct.isNumeric());
+				assertFalse(fct.isGeometry());
+			} catch(ParseException e1) {
+				e1.printStackTrace();
+				fail("This query contains a DECLARED UDF: this test should have succeeded!");
+			}
 
-		// Test the return type in function of the parameter:
-		try {
-			assertNotNull(parser.parseQuery("SELECT toto('blabla') AS toto1, toto(123) AS toto2, toto(POINT('', 1, 2)) AS toto3  FROM foo;"));
-		} catch(ParseException e1) {
-			e1.printStackTrace();
-			fail("This query contains two DECLARED UDFs used here: this test should have succeeded!");
-		}
-		try {
-			parser.parseQuery("SELECT toto('blabla') * 123 AS \"SuperError\" FROM foo;");
-			fail("This query contains a DECLARED UDF BUT used as numeric...which is here not possible: this test should have failed!");
-		} catch(ParseException e) {
-			assertTrue(e instanceof UnresolvedIdentifiersException);
-			UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e;
-			assertEquals(1, ex.getNbErrors());
-			assertEquals("Type mismatch! A numeric value was expected instead of \"toto('blabla')\".", ex.getErrors().next().getMessage());
-		}
-		try {
-			parser.parseQuery("SELECT toto(123) || 'blabla' AS \"SuperError\" FROM foo;");
-			fail("This query contains a DECLARED UDF BUT used as string...which is here not possible: this test should have failed!");
-		} catch(ParseException e) {
-			assertTrue(e instanceof UnresolvedIdentifiersException);
-			UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e;
-			assertEquals(1, ex.getNbErrors());
-			assertEquals("Type mismatch! A string value was expected instead of \"toto(123)\".", ex.getErrors().next().getMessage());
-		}
-		try {
-			parser.parseQuery("SELECT toto(POINT('', 1, 2)) || 'blabla' AS \"SuperError\" FROM foo;");
-			fail("This query contains a DECLARED UDF BUT used as string...which is here not possible: this test should have failed!");
-		} catch(ParseException e) {
-			assertTrue(e instanceof UnresolvedIdentifiersException);
-			UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e;
-			assertEquals(1, ex.getNbErrors());
-			assertEquals("Type mismatch! A string value was expected instead of \"toto(POINT('', 1, 2))\".", ex.getErrors().next().getMessage());
+			// Test the return type checking inside a whole query:
+			try {
+				assertNotNull(parser.parseQuery("SELECT tata()*3 AS \"aNumeric\" FROM foo;"));
+			} catch(ParseException e1) {
+				e1.printStackTrace();
+				fail("This query contains a DECLARED UDF multiplicated by 3: this test should have succeeded!");
+			}
+			try {
+				parser.parseQuery("SELECT 'Blabla ' || tata() AS \"SuperError\" FROM foo;");
+				fail("This query contains a DECLARED UDF BUT used as string...which is here not possible: this test should have failed!");
+			} catch(ParseException e1) {
+				assertTrue(e1 instanceof UnresolvedIdentifiersException);
+				UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e1;
+				assertEquals(1, ex.getNbErrors());
+				assertEquals("Type mismatch! A string value was expected instead of \"tata()\".", ex.getErrors().next().getMessage());
+			}
+			try {
+				parser.parseQuery("SELECT tata() || 'Blabla ' AS \"SuperError\" FROM foo;");
+				fail("This query contains a DECLARED UDF BUT used as string...which is here not possible: this test should have failed!");
+			} catch(ParseException e1) {
+				assertTrue(e1 instanceof UnresolvedIdentifiersException);
+				UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e1;
+				assertEquals(1, ex.getNbErrors());
+				assertEquals("Type mismatch! A string value was expected instead of \"tata()\".", ex.getErrors().next().getMessage());
+			}
+
+			// Test the return type of the function TITI generated by the parser:
+			try {
+				ADQLQuery query = parser.parseQuery("SELECT titi() FROM foo;");
+				ADQLOperand fct = query.getSelect().get(0).getOperand();
+				assertTrue(fct instanceof DefaultUDF);
+				assertNotNull(((DefaultUDF)fct).getDefinition());
+				assertFalse(fct.isString());
+				assertFalse(fct.isNumeric());
+				assertTrue(fct.isGeometry());
+			} catch(ParseException e1) {
+				e1.printStackTrace();
+				fail("This query contains a DECLARED UDF: this test should have succeeded!");
+			}
+
+			// Test the return type checking inside a whole query:
+			try {
+				parser.parseQuery("SELECT CONTAINS(colG, titi()) ' AS \"Super\" FROM foo;");
+				fail("Geometrical UDFs are not allowed for the moment in the ADQL language: this test should have failed!");
+			} catch(ParseException e1) {
+				assertTrue(e1 instanceof ParseException);
+				assertEquals(" Encountered \"(\". Was expecting one of: \")\" \".\" \".\" \")\" ", e1.getMessage());
+			}
+			try {
+				parser.parseQuery("SELECT titi()*3 AS \"SuperError\" FROM foo;");
+				fail("This query contains a DECLARED UDF BUT used as numeric...which is here not possible: this test should have failed!");
+			} catch(ParseException e1) {
+				assertTrue(e1 instanceof UnresolvedIdentifiersException);
+				UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e1;
+				assertEquals(1, ex.getNbErrors());
+				assertEquals("Type mismatch! A numeric value was expected instead of \"titi()\".", ex.getErrors().next().getMessage());
+			}
+
+			// Try with functions wrapped on 2 levels:
+			// i.e. fct1('blabla', fct2(fct3('blabla')))
+			try {
+				FunctionDef[] complexFcts = new FunctionDef[3];
+				complexFcts[0] = new FunctionDef("fct1", new DBType(DBDatatype.VARCHAR), new FunctionParam[]{ new FunctionParam("str", new DBType(DBDatatype.VARCHAR)), new FunctionParam("num", new DBType(DBDatatype.INTEGER)) });
+				complexFcts[1] = new FunctionDef("fct2", new DBType(DBDatatype.INTEGER), new FunctionParam[]{ new FunctionParam("str", new DBType(DBDatatype.VARCHAR)) });
+				complexFcts[2] = new FunctionDef("fct3", new DBType(DBDatatype.VARCHAR), new FunctionParam[]{ new FunctionParam("str", new DBType(DBDatatype.VARCHAR)) });
+				parser = new ADQLParser(version);
+				parser.setQueryChecker(new DBChecker(tables, Arrays.asList(complexFcts)));
+			} catch(ParseException pe) {
+				pe.printStackTrace();
+				fail("Failed initialization because of an invalid UDF declaration! Cause: (cf console)");
+			}
+
+			// With parameters of the good type:
+			try {
+				assertNotNull(parser.parseQuery("SELECT fct1('blabla', fct2(fct3('blabla'))) FROM foo"));
+			} catch(ParseException pe) {
+				pe.printStackTrace();
+				fail("Types are matching: this test should have succeeded!");
+			}
+			// With parameters of the bad type:
+			try {
+				parser.parseQuery("SELECT fct2(fct1('blabla', fct3('blabla'))) FROM foo");
+				fail("Parameters types are not matching: the parsing should have failed!");
+			} catch(ParseException pe) {
+				assertEquals(UnresolvedIdentifiersException.class, pe.getClass());
+				assertEquals(1, ((UnresolvedIdentifiersException)pe).getNbErrors());
+				ParseException innerPe = ((UnresolvedIdentifiersException)pe).getErrors().next();
+				assertEquals("Unresolved function: \"fct1('blabla', fct3('blabla'))\"! No UDF has been defined or found with the signature: fct1(STRING, STRING).", innerPe.getMessage());
+			}
+
+			// CLEAR ALL UDFs AND ALLOW UNKNOWN FUNCTION:
+			parser = new ADQLParser(version);
+			parser.setQueryChecker(new DBChecker(tables, null));
+
+			// Test again:
+			try {
+				assertNotNull(parser.parseQuery("SELECT toto() FROM foo;"));
+			} catch(ParseException e) {
+				e.printStackTrace();
+				fail("The parser allow ANY unknown function: this test should have succeeded!");
+			}
+
+			// Test the return type of the function generated by the parser:
+			try {
+				ADQLQuery query = parser.parseQuery("SELECT toto() FROM foo;");
+				ADQLOperand fct = query.getSelect().get(0).getOperand();
+				assertTrue(fct instanceof DefaultUDF);
+				assertNull(((DefaultUDF)fct).getDefinition());
+				assertTrue(fct.isString());
+				assertTrue(fct.isNumeric());
+			} catch(ParseException e1) {
+				e1.printStackTrace();
+				fail("The parser allow ANY unknown function: this test should have succeeded!");
+			}
+
+			// DECLARE THE UDF (while unknown functions are allowed):
+			try {
+				parser = new ADQLParser(version);
+				parser.setQueryChecker(new DBChecker(tables, Arrays.asList(new FunctionDef[]{ new FunctionDef("toto", new DBType(DBDatatype.VARCHAR)) })));
+			} catch(ParseException pe) {
+				pe.printStackTrace();
+				fail("Failed initialization because of an invalid UDF declaration! Cause: (cf console)");
+			}
+
+			// Test the return type of the function generated by the parser:
+			try {
+				ADQLQuery query = parser.parseQuery("SELECT toto() FROM foo;");
+				ADQLOperand fct = query.getSelect().get(0).getOperand();
+				assertTrue(fct instanceof DefaultUDF);
+				assertNotNull(((DefaultUDF)fct).getDefinition());
+				assertTrue(fct.isString());
+				assertFalse(fct.isNumeric());
+			} catch(ParseException e1) {
+				e1.printStackTrace();
+				fail("The parser allow ANY unknown function: this test should have succeeded!");
+			}
+
+			// DECLARE UDFs WITH SAME NAMES BUT DIFFERENT TYPE OF ARGUMENT:
+			try {
+				udfs = new FunctionDef[]{ new FunctionDef("toto", new DBType(DBDatatype.VARCHAR), new FunctionParam[]{ new FunctionParam("attr", new DBType(DBDatatype.VARCHAR)) }), new FunctionDef("toto", new DBType(DBDatatype.INTEGER), new FunctionParam[]{ new FunctionParam("attr", new DBType(DBDatatype.INTEGER)) }), new FunctionDef("toto", new DBType(DBDatatype.INTEGER), new FunctionParam[]{ new FunctionParam("attr", new DBType(DBDatatype.POINT)) }) };
+				parser = new ADQLParser(version);
+				parser.setQueryChecker(new DBChecker(tables, Arrays.asList(udfs)));
+			} catch(ParseException pe) {
+				pe.printStackTrace();
+				fail("Failed initialization because of an invalid UDF declaration! Cause: (cf console)");
+			}
+
+			// Test the return type in function of the parameter:
+			try {
+				assertNotNull(parser.parseQuery("SELECT toto('blabla') AS toto1, toto(123) AS toto2, toto(POINT('', 1, 2)) AS toto3  FROM foo;"));
+			} catch(ParseException e1) {
+				e1.printStackTrace();
+				fail("This query contains two DECLARED UDFs used here: this test should have succeeded!");
+			}
+			try {
+				parser.parseQuery("SELECT toto('blabla') * 123 AS \"SuperError\" FROM foo;");
+				fail("This query contains a DECLARED UDF BUT used as numeric...which is here not possible: this test should have failed!");
+			} catch(ParseException e) {
+				assertTrue(e instanceof UnresolvedIdentifiersException);
+				UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e;
+				assertEquals(1, ex.getNbErrors());
+				assertEquals("Type mismatch! A numeric value was expected instead of \"toto('blabla')\".", ex.getErrors().next().getMessage());
+			}
+			try {
+				parser.parseQuery("SELECT toto(123) || 'blabla' AS \"SuperError\" FROM foo;");
+				fail("This query contains a DECLARED UDF BUT used as string...which is here not possible: this test should have failed!");
+			} catch(ParseException e) {
+				assertTrue(e instanceof UnresolvedIdentifiersException);
+				UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e;
+				assertEquals(1, ex.getNbErrors());
+				assertEquals("Type mismatch! A string value was expected instead of \"toto(123)\".", ex.getErrors().next().getMessage());
+			}
+			try {
+				parser.parseQuery("SELECT toto(POINT('', 1, 2)) || 'blabla' AS \"SuperError\" FROM foo;");
+				fail("This query contains a DECLARED UDF BUT used as string...which is here not possible: this test should have failed!");
+			} catch(ParseException e) {
+				assertTrue(e instanceof UnresolvedIdentifiersException);
+				UnresolvedIdentifiersException ex = (UnresolvedIdentifiersException)e;
+				assertEquals(1, ex.getNbErrors());
+				assertEquals("Type mismatch! A string value was expected instead of \"toto(POINT('', 1, 2))\".", ex.getErrors().next().getMessage());
+			}
 		}
 	}
 
diff --git a/test/adql/parser/TestADQLParser.java b/test/adql/parser/TestADQLParser.java
index 6e3a76c537efbbc71441603746fac215439dddf8..6743b8ec66d6eb2d2323e680d7c46f1eeb7d3813 100644
--- a/test/adql/parser/TestADQLParser.java
+++ b/test/adql/parser/TestADQLParser.java
@@ -55,6 +55,36 @@ public class TestADQLParser {
 	public void tearDown() throws Exception {
 	}
 
+	@Test
+	public void testConstraintList() {
+		for(ADQLVersion version : ADQLVersion.values()) {
+			ADQLParser parser = new ADQLParser(version);
+
+			/* TEST: in a constraint (i.e. Constraint() in the grammar),
+			 *       avoid ambiguity between (OPERAND) and (CONSTRAINT) */
+			try {
+				parser.parseWhere("WHERE (mag + 2) < 5"); // CONSTRAINT = (OPERAND) COMP_OPERATOR OPERAND
+				parser.parseWhere("WHERE (mag < 2)");     // CONSTRAINT = (CONSTRAINT)
+			} catch(Exception ex) {
+				ex.printStackTrace();
+				fail("[ADQL-" + version + "] Unexpected error while parsing WHERE valid conditions! (see console for more details)");
+			}
+
+			// CASE: same test but this time with an incorrect function argument
+			/*
+			 * NOTE: If this expression is not correctly parsed, the raised
+			 *       error will be about CONTAINS instead of being about PI.
+			 */
+			try {
+				parser.parseWhere("WHERE CONTAINS(PI(), CIRCLE('', ra, dec)) = 1");
+				fail("PI() is not a valid argument of CONTAINS(...)!");
+			} catch(Exception ex) {
+				assertEquals(ParseException.class, ex.getClass());
+				assertTrue(ex.getMessage().startsWith(" Encountered \"PI\". Was expecting one of: \"BOX\" \"CENTROID\" \"CIRCLE\" \"POINT\" \"POLYGON\""));
+			}
+		}
+	}
+
 	@Test
 	public void testNumericFunctionParams() {
 
@@ -228,345 +258,497 @@ public class TestADQLParser {
 	}
 
 	@Test
-	public void testColumnReference() {
-		ADQLParser parser = new ADQLParser();
-		try {
-			// ORDER BY
-			parser.parseQuery("SELECT * FROM cat ORDER BY oid;");
-			parser.parseQuery("SELECT * FROM cat ORDER BY oid ASC;");
-			parser.parseQuery("SELECT * FROM cat ORDER BY oid DESC;");
-			parser.parseQuery("SELECT * FROM cat ORDER BY 1;");
-			parser.parseQuery("SELECT * FROM cat ORDER BY 1 ASC;");
-			parser.parseQuery("SELECT * FROM cat ORDER BY 1 DESC;");
-			// GROUP BY
-			parser.parseQuery("SELECT * FROM cat GROUP BY oid;");
-			parser.parseQuery("SELECT * FROM cat GROUP BY cat.oid;");
-			// JOIN ... USING(...)
-			parser.parseQuery("SELECT * FROM cat JOIN cat2 USING(oid);");
-		} catch(Exception e) {
-			e.printStackTrace(System.err);
-			fail("These ADQL queries are strictly correct! No error should have occured. (see stdout for more details)");
-		}
+	public void testGroupBy() {
+		for(ADQLVersion version : ADQLVersion.values()) {
+			ADQLParser parser = new ADQLParser(version);
 
-		try {
-			// ORDER BY
-			parser.parseQuery("SELECT * FROM cat ORDER BY cat.oid;");
-			fail("A qualified column name is forbidden in ORDER BY! This test should have failed.");
-		} catch(Exception e) {
-			assertEquals(ParseException.class, e.getClass());
-			assertEquals(" Encountered \".\". Was expecting one of: <EOF> \",\" \";\" \"ASC\" \"DESC\" ", e.getMessage());
-		}
+			// CASE: simple or qualified column name => OK!
+			try {
+				parser.parseQuery("SELECT * FROM cat GROUP BY oid;");
+				parser.parseQuery("SELECT * FROM cat GROUP BY cat.oid;");
+			} catch(Exception e) {
+				e.printStackTrace(System.err);
+				fail("These ADQL queries are strictly correct! No error should have occured. (see stdout for more details)");
+			}
 
-		// Query reported as in error before the bug correction:
-		try {
-			parser.parseQuery("SELECT TOP 10 browndwarfs.cat.jmag FROM browndwarfs.cat ORDER BY browndwarfs.cat.jmag");
-			fail("A qualified column name is forbidden in ORDER BY! This test should have failed.");
-		} catch(Exception e) {
-			assertEquals(ParseException.class, e.getClass());
-			assertEquals(" Encountered \".\". Was expecting one of: <EOF> \",\" \";\" \"ASC\" \"DESC\" ", e.getMessage());
-		}
+			/* CASE: in ADQL-2.0, only a column is allowed (on the contrary to
+			 *       ORDER BY which allows also an index of a selected column)
+			 *       => ERROR! */
+			final String Q_INDEX = "SELECT * FROM cat GROUP BY 1;";
+			if (version == ADQLVersion.V2_0) {
+				try {
+					// GROUP BY with a SELECT item index
+					parser.parseQuery(Q_INDEX);
+					fail("A SELECT item index is forbidden in GROUP BY! This test should have failed.");
+				} catch(Exception e) {
+					assertEquals(ParseException.class, e.getClass());
+					assertEquals(" Encountered \"1\". Was expecting one of: \"\\\"\" <REGULAR_IDENTIFIER_CANDIDATE> ", e.getMessage());
+				}
+			} else {
+				try {
+					parser.parseQuery(Q_INDEX);
+				} catch(Exception e) {
+					e.printStackTrace(System.err);
+					fail("These ADQL queries are strictly correct! No error should have occured. (see stdout for more details)");
+				}
+			}
 
-		try {
-			// GROUP BY with a SELECT item index
-			parser.parseQuery("SELECT * FROM cat GROUP BY 1;");
-			fail("A SELECT item index is forbidden in GROUP BY! This test should have failed.");
-		} catch(Exception e) {
-			assertEquals(ParseException.class, e.getClass());
-			assertEquals(" Encountered \"1\". Was expecting one of: \"\\\"\" <REGULAR_IDENTIFIER_CANDIDATE> ", e.getMessage());
+			// CASE: only from ADQL-2.1, any ADQL expression/operand => OK!
+			final String Q1 = "SELECT * FROM cat GROUP BY 1+2;", Q2 = "SELECT * FROM cat GROUP BY sqrt(foo);";
+			if (version == ADQLVersion.V2_0) {
+				try {
+					parser.parseQuery(Q1);
+					fail("In ADQL-v2.0, GROUP BY with an expression is forbidden!");
+				} catch(Exception e) {
+					assertEquals(ParseException.class, e.getClass());
+					assertEquals(" Encountered \"1\". Was expecting one of: \"\\\"\" <REGULAR_IDENTIFIER_CANDIDATE> ", e.getMessage());
+				}
+				try {
+					parser.parseQuery(Q2);
+					fail("In ADQL-v2.0, GROUP BY with an expression is forbidden!");
+				} catch(Exception e) {
+					assertEquals(ParseException.class, e.getClass());
+					assertEquals(" Encountered \"sqrt\". Was expecting one of: \"\\\"\" <REGULAR_IDENTIFIER_CANDIDATE> \n" + "(HINT: \"sqrt\" is a reserved ADQL word in v2.0. To use it as a column/table/schema name/alias, write it between double quotes.)", e.getMessage());
+				}
+			} else {
+				try {
+					parser.parseQuery(Q1);
+					parser.parseQuery(Q2);
+				} catch(Exception e) {
+					e.printStackTrace(System.err);
+					fail("In ADQL-" + version + ", GROUP BY with expression SHOULD be allowed. (see console for more details)");
+				}
+			}
 		}
+	}
 
-		try {
-			// JOIN ... USING(...)
-			parser.parseQuery("SELECT * FROM cat JOIN cat2 USING(cat.oid);");
-			fail("A qualified column name is forbidden in USING(...)! This test should have failed.");
-		} catch(Exception e) {
-			assertEquals(ParseException.class, e.getClass());
-			assertEquals(" Encountered \".\". Was expecting one of: \")\" \",\" ", e.getMessage());
+	@Test
+	public void testOrderBy() {
+		for(ADQLVersion version : ADQLVersion.values()) {
+			ADQLParser parser = new ADQLParser(version);
+			try {
+				// CASE: Simple column name
+				ADQLQuery query;
+				query = parser.parseQuery("SELECT * FROM cat ORDER BY oid;");
+				assertNotNull(query.getOrderBy().get(0).getExpression());
+				query = parser.parseQuery("SELECT * FROM cat ORDER BY oid ASC;");
+				assertNotNull(query.getOrderBy().get(0).getExpression());
+				query = parser.parseQuery("SELECT * FROM cat ORDER BY oid DESC;");
+				assertNotNull(query.getOrderBy().get(0).getExpression());
+
+				// CASE: selected column reference
+				query = parser.parseQuery("SELECT * FROM cat ORDER BY 1;");
+				assertNotNull(query.getOrderBy().get(0).getColumnReference());
+				query = parser.parseQuery("SELECT * FROM cat ORDER BY 1 ASC;");
+				assertNotNull(query.getOrderBy().get(0).getColumnReference());
+				query = parser.parseQuery("SELECT * FROM cat ORDER BY 1 DESC;");
+				assertNotNull(query.getOrderBy().get(0).getColumnReference());
+			} catch(Exception e) {
+				e.printStackTrace(System.err);
+				fail("These ADQL queries are strictly correct! No error should have occured. (cf console for more details)");
+			}
+
+			// CASE: only in ADQL-2.0, qualified columns are forbidden
+			String Q_QUAL_COL = "SELECT * FROM cat ORDER BY cat.oid;";
+			if (version == ADQLVersion.V2_0) {
+				try {
+					parser.parseQuery(Q_QUAL_COL);
+					fail("In ADQL-v2.0, a qualified column name is forbidden in ORDER BY! This test should have failed.");
+				} catch(Exception e) {
+					assertEquals(ParseException.class, e.getClass());
+					assertEquals(" Encountered \".\". Was expecting one of: <EOF> \",\" \";\" \"ASC\" \"DESC\" ", e.getMessage());
+				}
+			} else {
+				try {
+					parser.parseQuery(Q_QUAL_COL);
+				} catch(Exception e) {
+					e.printStackTrace();
+					fail("In ADQL-" + version + ", ORDER BY with a qualified column name should be allowed! (see console for more details)");
+				}
+			}
+
+			// CASE: Query reported as in error before a bug correction:
+			/*
+			 * NOTE: same as above but with a real bug report.
+			 */
+			Q_QUAL_COL = "SELECT TOP 10 browndwarfs.cat.jmag FROM browndwarfs.cat ORDER BY browndwarfs.cat.jmag";
+			if (version == ADQLVersion.V2_0) {
+				try {
+					parser.parseQuery(Q_QUAL_COL);
+					fail("A qualified column name is forbidden in ORDER BY! This test should have failed.");
+				} catch(Exception e) {
+					assertEquals(ParseException.class, e.getClass());
+					assertEquals(" Encountered \".\". Was expecting one of: <EOF> \",\" \";\" \"ASC\" \"DESC\" ", e.getMessage());
+				}
+			} else {
+				try {
+					parser.parseQuery(Q_QUAL_COL);
+				} catch(Exception e) {
+					e.printStackTrace();
+					fail("In ADQL-" + version + ", ORDER BY with a qualified column name should be allowed! (see console for more details)");
+				}
+			}
+
+			// CASE: only from ADQL-2.1, any ADQL expression/operand => OK!
+			final String Q1 = "SELECT * FROM cat ORDER BY 1+2;", Q2 = "SELECT * FROM cat ORDER BY sqrt(foo);";
+			if (version == ADQLVersion.V2_0) {
+				try {
+					parser.parseQuery(Q1);
+					fail("In ADQL-v2.0, ORDER BY with an expression is forbidden!");
+				} catch(Exception e) {
+					assertEquals(ParseException.class, e.getClass());
+					assertEquals(" Encountered \"+\". Was expecting one of: <EOF> \",\" \";\" \"ASC\" \"DESC\" ", e.getMessage());
+				}
+				try {
+					parser.parseQuery(Q2);
+					fail("In ADQL-v2.0, ORDER BY with an expression is forbidden!");
+				} catch(Exception e) {
+					assertEquals(ParseException.class, e.getClass());
+					assertEquals(" Encountered \"sqrt\". Was expecting one of: <UNSIGNED_INTEGER> \"\\\"\" <REGULAR_IDENTIFIER_CANDIDATE> \n" + "(HINT: \"sqrt\" is a reserved ADQL word in v2.0. To use it as a column/table/schema name/alias, write it between double quotes.)", e.getMessage());
+				}
+			} else {
+				try {
+					parser.parseQuery(Q1);
+					parser.parseQuery(Q2);
+				} catch(Exception e) {
+					e.printStackTrace(System.err);
+					fail("In ADQL-" + version + ", ORDER BY with expression SHOULD be allowed. (see console for more details)");
+				}
+			}
 		}
+	}
 
-		try {
-			// JOIN ... USING(...)
-			parser.parseQuery("SELECT * FROM cat JOIN cat2 USING(1);");
-			fail("A column index is forbidden in USING(...)! This test should have failed.");
-		} catch(Exception e) {
-			assertEquals(ParseException.class, e.getClass());
-			assertEquals(" Encountered \"1\". Was expecting one of: \"\\\"\" <REGULAR_IDENTIFIER_CANDIDATE> ", e.getMessage());
+	@Test
+	public void testJoinUsing() {
+		for(ADQLVersion version : ADQLVersion.values()) {
+			ADQLParser parser = new ADQLParser(version);
+			try {
+				// JOIN ... USING(...)
+				parser.parseQuery("SELECT * FROM cat JOIN cat2 USING(oid);");
+			} catch(Exception e) {
+				e.printStackTrace(System.err);
+				fail("This ADQL query is strictly correct! No error should have occured. (see stdout for more details)");
+			}
+			try {
+				// JOIN ... USING(...)
+				parser.parseQuery("SELECT * FROM cat JOIN cat2 USING(cat.oid);");
+				fail("A qualified column name is forbidden in USING(...)! This test should have failed.");
+			} catch(Exception e) {
+				assertEquals(ParseException.class, e.getClass());
+				assertEquals(" Encountered \".\". Was expecting one of: \")\" \",\" ", e.getMessage());
+			}
+
+			try {
+				// JOIN ... USING(...)
+				parser.parseQuery("SELECT * FROM cat JOIN cat2 USING(1);");
+				fail("A column index is forbidden in USING(...)! This test should have failed.");
+			} catch(Exception e) {
+				assertEquals(ParseException.class, e.getClass());
+				assertEquals(" Encountered \"1\". Was expecting one of: \"\\\"\" <REGULAR_IDENTIFIER_CANDIDATE> ", e.getMessage());
+			}
 		}
 	}
 
 	@Test
 	public void testDelimitedIdentifiersWithDot() {
-		ADQLParser parser = new ADQLParser();
-		try {
-			ADQLQuery query = parser.parseQuery("SELECT * FROM \"B/avo.rad/catalog\";");
-			assertEquals("B/avo.rad/catalog", query.getFrom().getTables().get(0).getTableName());
-		} catch(Exception e) {
-			e.printStackTrace(System.err);
-			fail("The ADQL query is strictly correct! No error should have occured. (see stdout for more details)");
+		for(ADQLVersion version : ADQLVersion.values()) {
+			ADQLParser parser = new ADQLParser(version);
+			try {
+				ADQLQuery query = parser.parseQuery("SELECT * FROM \"B/avo.rad/catalog\";");
+				assertEquals("B/avo.rad/catalog", query.getFrom().getTables().get(0).getTableName());
+			} catch(Exception e) {
+				e.printStackTrace(System.err);
+				fail("The ADQL query is strictly correct! No error should have occured. (see stdout for more details)");
+			}
 		}
 	}
 
 	@Test
 	public void testJoinTree() {
-		ADQLParser parser = new ADQLParser();
-		try {
-			String[] queries = new String[]{ "SELECT * FROM aTable A JOIN aSecondTable B ON A.id = B.id JOIN aThirdTable C ON B.id = C.id;", "SELECT * FROM aTable A NATURAL JOIN aSecondTable B NATURAL JOIN aThirdTable C;" };
-			for(String q : queries) {
-				ADQLQuery query = parser.parseQuery(q);
-
-				assertTrue(query.getFrom() instanceof ADQLJoin);
-
-				ADQLJoin join = ((ADQLJoin)query.getFrom());
-				assertTrue(join.getLeftTable() instanceof ADQLJoin);
-				assertTrue(join.getRightTable() instanceof ADQLTable);
-				assertEquals("aThirdTable", ((ADQLTable)join.getRightTable()).getTableName());
-
-				join = (ADQLJoin)join.getLeftTable();
-				assertTrue(join.getLeftTable() instanceof ADQLTable);
-				assertEquals("aTable", ((ADQLTable)join.getLeftTable()).getTableName());
-				assertTrue(join.getRightTable() instanceof ADQLTable);
-				assertEquals("aSecondTable", ((ADQLTable)join.getRightTable()).getTableName());
+		for(ADQLVersion version : ADQLVersion.values()) {
+			ADQLParser parser = new ADQLParser(version);
+			try {
+				String[] queries = new String[]{ "SELECT * FROM aTable A JOIN aSecondTable B ON A.id = B.id JOIN aThirdTable C ON B.id = C.id;", "SELECT * FROM aTable A NATURAL JOIN aSecondTable B NATURAL JOIN aThirdTable C;" };
+				for(String q : queries) {
+					ADQLQuery query = parser.parseQuery(q);
+
+					assertTrue(query.getFrom() instanceof ADQLJoin);
+
+					ADQLJoin join = ((ADQLJoin)query.getFrom());
+					assertTrue(join.getLeftTable() instanceof ADQLJoin);
+					assertTrue(join.getRightTable() instanceof ADQLTable);
+					assertEquals("aThirdTable", ((ADQLTable)join.getRightTable()).getTableName());
+
+					join = (ADQLJoin)join.getLeftTable();
+					assertTrue(join.getLeftTable() instanceof ADQLTable);
+					assertEquals("aTable", ((ADQLTable)join.getLeftTable()).getTableName());
+					assertTrue(join.getRightTable() instanceof ADQLTable);
+					assertEquals("aSecondTable", ((ADQLTable)join.getRightTable()).getTableName());
+				}
+			} catch(Exception e) {
+				e.printStackTrace(System.err);
+				fail("The ADQL query is strictly correct! No error should have occured. (see stdout for more details)");
 			}
-		} catch(Exception e) {
-			e.printStackTrace(System.err);
-			fail("The ADQL query is strictly correct! No error should have occured. (see stdout for more details)");
 		}
 	}
 
 	@Test
 	public void test() {
-		ADQLParser parser = new ADQLParser();
-		try {
-			ADQLQuery query = parser.parseQuery("SELECT 'truc''machin'  	'bidule' --- why not a comment now ^^\n'FIN' FROM foo;");
-			assertNotNull(query);
-			assertEquals("truc'machinbiduleFIN", ((StringConstant)(query.getSelect().get(0).getOperand())).getValue());
-			assertEquals("'truc''machinbiduleFIN'", query.getSelect().get(0).getOperand().toADQL());
-		} catch(Exception ex) {
-			fail("String litteral concatenation is perfectly legal according to the ADQL standard.");
-		}
+		for(ADQLVersion version : ADQLVersion.values()) {
+			ADQLParser parser = new ADQLParser(version);
+			try {
+				ADQLQuery query = parser.parseQuery("SELECT 'truc''machin'  	'bidule' --- why not a comment now ^^\n'FIN' FROM foo;");
+				assertNotNull(query);
+				assertEquals("truc'machinbiduleFIN", ((StringConstant)(query.getSelect().get(0).getOperand())).getValue());
+				assertEquals("'truc''machinbiduleFIN'", query.getSelect().get(0).getOperand().toADQL());
+			} catch(Exception ex) {
+				fail("String litteral concatenation is perfectly legal according to the ADQL standard.");
+			}
 
-		// With a comment ending the query
-		try {
-			ADQLQuery query = parser.parseQuery("SELECT TOP 1 * FROM ivoa.ObsCore -- comment");
-			assertNotNull(query);
-		} catch(Exception ex) {
-			ex.printStackTrace();
-			fail("String litteral concatenation is perfectly legal according to the ADQL standard.");
+			// With a comment ending the query
+			try {
+				ADQLQuery query = parser.parseQuery("SELECT TOP 1 * FROM ivoa.ObsCore -- comment");
+				assertNotNull(query);
+			} catch(Exception ex) {
+				ex.printStackTrace();
+				fail("String litteral concatenation is perfectly legal according to the ADQL standard.");
+			}
 		}
 	}
 
 	@Test
 	public void testIncorrectCharacter() {
-		/* An identifier must be written only with digits, an underscore or
-		 * regular latin characters: */
-		try {
-			(new ADQLParser()).parseQuery("select gr\u00e9gory FROM aTable");
-		} catch(Throwable t) {
-			assertEquals(ParseException.class, t.getClass());
-			assertTrue(t.getMessage().startsWith("Incorrect character encountered at l.1, c.10: "));
-			assertTrue(t.getMessage().endsWith("Possible cause: a non-ASCI/UTF-8 character (solution: remove/replace it)."));
-		}
+		for(ADQLVersion version : ADQLVersion.values()) {
+			/* An identifier must be written only with digits, an underscore or
+			 * regular latin characters: */
+			try {
+				(new ADQLParser(version)).parseQuery("select gr\u00e9gory FROM aTable");
+			} catch(Throwable t) {
+				assertEquals(ParseException.class, t.getClass());
+				assertTrue(t.getMessage().startsWith("Incorrect character encountered at l.1, c.10: "));
+				assertTrue(t.getMessage().endsWith("Possible cause: a non-ASCI/UTF-8 character (solution: remove/replace it)."));
+			}
 
-		/* Un-finished double/single quoted string: */
-		try {
-			(new ADQLParser()).parseQuery("select \"stuff FROM aTable");
-		} catch(Throwable t) {
-			assertEquals(ParseException.class, t.getClass());
-			assertTrue(t.getMessage().startsWith("Incorrect character encountered at l.1, c.26: <EOF>"));
-			assertTrue(t.getMessage().endsWith("Possible cause: a string between single or double quotes which is never closed (solution: well...just close it!)."));
-		}
+			/* Un-finished double/single quoted string: */
+			try {
+				(new ADQLParser(version)).parseQuery("select \"stuff FROM aTable");
+			} catch(Throwable t) {
+				assertEquals(ParseException.class, t.getClass());
+				assertTrue(t.getMessage().startsWith("Incorrect character encountered at l.1, c.26: <EOF>"));
+				assertTrue(t.getMessage().endsWith("Possible cause: a string between single or double quotes which is never closed (solution: well...just close it!)."));
+			}
 
-		// But in a string, delimited identifier or a comment, it is fine:
-		try {
-			(new ADQLParser()).parseQuery("select 'gr\u00e9gory' FROM aTable");
-			(new ADQLParser()).parseQuery("select \"gr\u00e9gory\" FROM aTable");
-			(new ADQLParser()).parseQuery("select * FROM aTable -- a comment by Gr\u00e9gory");
-		} catch(Throwable t) {
-			fail("This error should never occurs because all these queries have an accentuated character but at a correct place.");
+			// But in a string, delimited identifier or a comment, it is fine:
+			try {
+				(new ADQLParser(version)).parseQuery("select 'gr\u00e9gory' FROM aTable");
+				(new ADQLParser(version)).parseQuery("select \"gr\u00e9gory\" FROM aTable");
+				(new ADQLParser(version)).parseQuery("select * FROM aTable -- a comment by Gr\u00e9gory");
+			} catch(Throwable t) {
+				fail("This error should never occurs because all these queries have an accentuated character but at a correct place.");
+			}
 		}
 	}
 
 	@Test
 	public void testMultipleSpacesInOrderAndGroupBy() {
-		try {
-			ADQLParser parser = new ADQLParser();
+		for(ADQLVersion version : ADQLVersion.values()) {
+			try {
+				ADQLParser parser = new ADQLParser(version);
 
-			// Single space:
-			parser.parseQuery("select * from aTable ORDER BY aCol");
-			parser.parseQuery("select * from aTable GROUP BY aCol");
+				// Single space:
+				parser.parseQuery("select * from aTable ORDER BY aCol");
+				parser.parseQuery("select * from aTable GROUP BY aCol");
 
-			// More than one space:
-			parser.parseQuery("select * from aTable ORDER      BY aCol");
-			parser.parseQuery("select * from aTable GROUP      BY aCol");
+				// More than one space:
+				parser.parseQuery("select * from aTable ORDER      BY aCol");
+				parser.parseQuery("select * from aTable GROUP      BY aCol");
 
-			// With any other space character:
-			parser.parseQuery("select * from aTable ORDER\tBY aCol");
-			parser.parseQuery("select * from aTable ORDER\nBY aCol");
-			parser.parseQuery("select * from aTable ORDER \t\nBY aCol");
+				// With any other space character:
+				parser.parseQuery("select * from aTable ORDER\tBY aCol");
+				parser.parseQuery("select * from aTable ORDER\nBY aCol");
+				parser.parseQuery("select * from aTable ORDER \t\nBY aCol");
 
-			parser.parseQuery("select * from aTable GROUP\tBY aCol");
-			parser.parseQuery("select * from aTable GROUP\nBY aCol");
-			parser.parseQuery("select * from aTable GROUP \t\nBY aCol");
-		} catch(Throwable t) {
-			t.printStackTrace();
-			fail("Having multiple space characters between the ORDER/GROUP and the BY keywords should not generate any parsing error.");
+				parser.parseQuery("select * from aTable GROUP\tBY aCol");
+				parser.parseQuery("select * from aTable GROUP\nBY aCol");
+				parser.parseQuery("select * from aTable GROUP \t\nBY aCol");
+			} catch(Throwable t) {
+				t.printStackTrace();
+				fail("Having multiple space characters between the ORDER/GROUP and the BY keywords should not generate any parsing error.");
+			}
 		}
 	}
 
 	@Test
 	public void testADQLReservedWord() {
-		ADQLParser parser = new ADQLParser();
+		for(ADQLVersion version : ADQLVersion.values()) {
+			ADQLParser parser = new ADQLParser(version);
 
-		final String hintAbs = ".*\n\\(HINT: \"abs\" is a reserved ADQL word in v[0-9]+\\.[0-9]+\\. To use it as a column/table/schema name/alias, write it between double quotes\\.\\)";
-		final String hintPoint = ".*\n\\(HINT: \"point\" is a reserved ADQL word in v[0-9]+\\.[0-9]+\\. To use it as a column/table/schema name/alias, write it between double quotes\\.\\)";
-		final String hintExists = ".*\n\\(HINT: \"exists\" is a reserved ADQL word in v[0-9]+\\.[0-9]+\\. To use it as a column/table/schema name/alias, write it between double quotes\\.\\)";
-		final String hintLike = ".*\n\\(HINT: \"LIKE\" is a reserved ADQL word in v[0-9]+\\.[0-9]+\\. To use it as a column/table/schema name/alias, write it between double quotes\\.\\)";
+			final String hintAbs = ".*\n\\(HINT: \"abs\" is a reserved ADQL word in v[0-9]+\\.[0-9]+\\. To use it as a column/table/schema name/alias, write it between double quotes\\.\\)";
+			final String hintPoint = ".*\n\\(HINT: \"point\" is a reserved ADQL word in v[0-9]+\\.[0-9]+\\. To use it as a column/table/schema name/alias, write it between double quotes\\.\\)";
+			final String hintExists = ".*\n\\(HINT: \"exists\" is a reserved ADQL word in v[0-9]+\\.[0-9]+\\. To use it as a column/table/schema name/alias, write it between double quotes\\.\\)";
+			final String hintLike = ".*\n\\(HINT: \"LIKE\" is a reserved ADQL word in v[0-9]+\\.[0-9]+\\. To use it as a column/table/schema name/alias, write it between double quotes\\.\\)";
 
-		/* TEST AS A COLUMN/TABLE/SCHEMA NAME... */
-		// ...with a numeric function name (but no param):
-		try {
-			parser.parseQuery("select abs from aTable");
-		} catch(Throwable t) {
-			assertEquals(ParseException.class, t.getClass());
-			assertTrue(t.getMessage().matches(hintAbs));
-		}
-		// ...with a geometric function name (but no param):
-		try {
-			parser.parseQuery("select point from aTable");
-		} catch(Throwable t) {
-			assertEquals(ParseException.class, t.getClass());
-			assertTrue(t.getMessage().matches(hintPoint));
-		}
-		// ...with an ADQL function name (but no param):
-		try {
-			parser.parseQuery("select exists from aTable");
-		} catch(Throwable t) {
-			assertEquals(ParseException.class, t.getClass());
-			assertTrue(t.getMessage().matches(hintExists));
-		}
-		// ...with an ADQL syntax item:
-		try {
-			parser.parseQuery("select LIKE from aTable");
-		} catch(Throwable t) {
-			assertEquals(ParseException.class, t.getClass());
-			assertTrue(t.getMessage().matches(hintLike));
-		}
+			/* TEST AS A COLUMN/TABLE/SCHEMA NAME... */
+			// ...with a numeric function name (but no param):
+			try {
+				parser.parseQuery("select abs from aTable");
+			} catch(Throwable t) {
+				assertEquals(ParseException.class, t.getClass());
+				assertTrue(t.getMessage().matches(hintAbs));
+			}
+			// ...with a geometric function name (but no param):
+			try {
+				parser.parseQuery("select point from aTable");
+			} catch(Throwable t) {
+				assertEquals(ParseException.class, t.getClass());
+				assertTrue(t.getMessage().matches(hintPoint));
+			}
+			// ...with an ADQL function name (but no param):
+			try {
+				parser.parseQuery("select exists from aTable");
+			} catch(Throwable t) {
+				assertEquals(ParseException.class, t.getClass());
+				assertTrue(t.getMessage().matches(hintExists));
+			}
+			// ...with an ADQL syntax item:
+			try {
+				parser.parseQuery("select LIKE from aTable");
+			} catch(Throwable t) {
+				assertEquals(ParseException.class, t.getClass());
+				assertTrue(t.getMessage().matches(hintLike));
+			}
 
-		/* TEST AS AN ALIAS... */
-		// ...with a numeric function name (but no param):
-		try {
-			parser.parseQuery("select aCol AS abs from aTable");
-		} catch(Throwable t) {
-			assertEquals(ParseException.class, t.getClass());
-			assertTrue(t.getMessage().matches(hintAbs));
-		}
-		// ...with a geometric function name (but no param):
-		try {
-			parser.parseQuery("select aCol AS point from aTable");
-		} catch(Throwable t) {
-			assertEquals(ParseException.class, t.getClass());
-			assertTrue(t.getMessage().matches(hintPoint));
-		}
-		// ...with an ADQL function name (but no param):
-		try {
-			parser.parseQuery("select aCol AS exists from aTable");
-		} catch(Throwable t) {
-			assertEquals(ParseException.class, t.getClass());
-			assertTrue(t.getMessage().matches(hintExists));
-		}
-		// ...with an ADQL syntax item:
-		try {
-			parser.parseQuery("select aCol AS LIKE from aTable");
-		} catch(Throwable t) {
-			assertEquals(ParseException.class, t.getClass());
-			assertTrue(t.getMessage().matches(hintLike));
-		}
+			/* TEST AS AN ALIAS... */
+			// ...with a numeric function name (but no param):
+			try {
+				parser.parseQuery("select aCol AS abs from aTable");
+			} catch(Throwable t) {
+				assertEquals(ParseException.class, t.getClass());
+				assertTrue(t.getMessage().matches(hintAbs));
+			}
+			// ...with a geometric function name (but no param):
+			try {
+				parser.parseQuery("select aCol AS point from aTable");
+			} catch(Throwable t) {
+				assertEquals(ParseException.class, t.getClass());
+				assertTrue(t.getMessage().matches(hintPoint));
+			}
+			// ...with an ADQL function name (but no param):
+			try {
+				parser.parseQuery("select aCol AS exists from aTable");
+			} catch(Throwable t) {
+				assertEquals(ParseException.class, t.getClass());
+				assertTrue(t.getMessage().matches(hintExists));
+			}
+			// ...with an ADQL syntax item:
+			try {
+				parser.parseQuery("select aCol AS LIKE from aTable");
+			} catch(Throwable t) {
+				assertEquals(ParseException.class, t.getClass());
+				assertTrue(t.getMessage().matches(hintLike));
+			}
 
-		/* TEST AT THE END OF THE QUERY (AND IN A WHERE) */
-		try {
-			parser.parseQuery("select aCol from aTable WHERE toto = abs");
-		} catch(Throwable t) {
-			assertEquals(ParseException.class, t.getClass());
-			assertTrue(t.getMessage().matches(hintAbs));
+			/* TEST AT THE END OF THE QUERY (AND IN A WHERE) */
+			try {
+				parser.parseQuery("select aCol from aTable WHERE toto = abs");
+			} catch(Throwable t) {
+				assertEquals(ParseException.class, t.getClass());
+				assertTrue(t.getMessage().matches(hintAbs));
+			}
 		}
 	}
 
 	@Test
 	public void testSQLReservedWord() {
-		ADQLParser parser = new ADQLParser();
+		for(ADQLVersion version : ADQLVersion.values()) {
+			ADQLParser parser = new ADQLParser(version);
 
-		try {
-			parser.parseQuery("SELECT rows FROM aTable");
-			fail("\"ROWS\" is an SQL reserved word. This query should not pass.");
-		} catch(Throwable t) {
-			assertEquals(ParseException.class, t.getClass());
-			assertTrue(t.getMessage().matches(".*\n\\(HINT: \"rows\" is not supported in ADQL v[0-9]+\\.[0-9]+, but is however a reserved word\\. To use it as a column/table/schema name/alias, write it between double quotes\\.\\)"));
-		}
+			try {
+				parser.parseQuery("SELECT rows FROM aTable");
+				fail("\"ROWS\" is an SQL reserved word. This query should not pass.");
+			} catch(Throwable t) {
+				assertEquals(ParseException.class, t.getClass());
+				assertTrue(t.getMessage().matches(".*\n\\(HINT: \"rows\" is not supported in ADQL v[0-9]+\\.[0-9]+, but is however a reserved word\\. To use it as a column/table/schema name/alias, write it between double quotes\\.\\)"));
+			}
 
-		try {
-			parser.parseQuery("SELECT CASE WHEN aCol = 2 THEN 'two' ELSE 'smth else' END as str FROM aTable");
-			fail("ADQL does not support the CASE syntax. This query should not pass.");
-		} catch(Throwable t) {
-			assertEquals(ParseException.class, t.getClass());
-			assertTrue(t.getMessage().matches(".*\n\\(HINT: \"CASE\" is not supported in ADQL v[0-9]+\\.[0-9]+, but is however a reserved word\\. To use it as a column/table/schema name/alias, write it between double quotes\\.\\)"));
+			try {
+				parser.parseQuery("SELECT CASE WHEN aCol = 2 THEN 'two' ELSE 'smth else' END as str FROM aTable");
+				fail("ADQL does not support the CASE syntax. This query should not pass.");
+			} catch(Throwable t) {
+				assertEquals(ParseException.class, t.getClass());
+				assertTrue(t.getMessage().matches(".*\n\\(HINT: \"CASE\" is not supported in ADQL v[0-9]+\\.[0-9]+, but is however a reserved word\\. To use it as a column/table/schema name/alias, write it between double quotes\\.\\)"));
+			}
 		}
 	}
 
 	@Test
 	public void testUDFName() {
-		ADQLParser parser = new ADQLParser();
-
-		// CASE: Valid UDF name => OK
-		try {
-			parser.parseQuery("SELECT foo(p1,p2) FROM aTable");
-		} catch(Throwable t) {
-			t.printStackTrace();
-			fail("Unexpected parsing error! This query should have passed. (see console for more details)");
-		}
+		for(ADQLVersion version : ADQLVersion.values()) {
+			ADQLParser parser = new ADQLParser(version);
 
+<<<<<<< HEAD
 		// CASE: Invalid UDF name => ParseException
 		final String[] functionsToTest = new String[]{ "_foo", "2do", "do!" };
 		for(String fct : functionsToTest){
 			try{
 				parser.parseQuery("SELECT " + fct + "(p1,p2) FROM aTable");
 				fail("A UDF name like \"" + fct + "\" is not allowed by the ADQL grammar. This query should not pass.");
+=======
+			// CASE: Valid UDF name => OK
+			try {
+				parser.parseQuery("SELECT foo(p1,p2) FROM aTable");
+>>>>>>> [ADQL] 4 commits in one: 1/ new syntax of ORDER and GROUP BY, 2/ fix positions
 			} catch(Throwable t) {
-				assertEquals(ParseException.class, t.getClass());
-				assertEquals("Invalid (User Defined) Function name: \"" + fct + "\"!", t.getMessage());
+				t.printStackTrace();
+				fail("Unexpected parsing error! This query should have passed. (see console for more details)");
+			}
+
+			// CASE: Invalid UDF name => ParseException
+			final String[] functionsToTest = new String[]{ "_foo", "2do", "do!" };
+			for(String fct : functionsToTest) {
+				try {
+					parser.parseQuery("SELECT " + fct + "(p1,p2) FROM aTable");
+					fail("A UDF name like \"" + fct + "\" is not allowed by the ADQL grammar. This query should not pass.");
+				} catch(Throwable t) {
+					assertEquals(ParseException.class, t.getClass());
+					assertEquals("Invalid (User Defined) Function name: \"" + fct + "\"!", t.getMessage());
+				}
 			}
 		}
 	}
 
 	@Test
 	public void testUDFDeclaration() {
-		ADQLParser parser = new ADQLParser();
+		for(ADQLVersion version : ADQLVersion.values()) {
+			ADQLParser parser = new ADQLParser(version);
 
-		// 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: 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: 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)");
+			// 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)");
+			}
 		}
 	}
 
@@ -650,79 +832,81 @@ 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!");
-		}
+		for(ADQLVersion version : ADQLVersion.values()) {
+			// DECLARE A SIMPLE PARSER where all geometries are allowed by default:
+			ADQLParser parser = new ADQLParser(version);
 
-		// 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);
+			// 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!");
+			}
 
-			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 with several geometries while only the allowed ones:
+			try {
+				parser = new ADQLParser(version);
+				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());
-		}
+			// TODO Test by adding REGION: // Only possible with ADQL-2.0 since in ADQL-2.1, REGION has been removed!
+			try {
+				parser = new ADQLParser(ADQLVersion.V2_0);
+				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("[ADQL-" + parser.getADQLVersion() + "] 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 with several geometries while none geometry is allowed:
+			try {
+				parser = new ADQLParser(version);
+				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());
+			}
 		}
 	}
 
@@ -757,95 +941,107 @@ public class TestADQLParser {
 
 	@Test
 	public void testCoordSys() {
-		// DECLARE A SIMPLE PARSER where all coordinate systems are allowed by default:
-		ADQLParser parser = new ADQLParser();
+		for(ADQLVersion version : ADQLVersion.values()) {
+			// DECLARE A SIMPLE PARSER where all coordinate systems are allowed by default:
+			ADQLParser parser = new ADQLParser(version);
 
-		// 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 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;"));
+				if (version == ADQLVersion.V2_0) {
+					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!");
+			}
 
-		// 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());
-		}
+			// 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 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 with several coordinate systems while only some allowed:
+			try {
+				parser = new ADQLParser(version);
+				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;"));
+				if (version == ADQLVersion.V2_0) {
+					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());
+			}
+			if (version == ADQLVersion.V2_0) {
+				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(version);
+				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;"));
+				if (version == ADQLVersion.V2_0) {
+					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());
+			}
+			if (version == ADQLVersion.V2_0) {
+				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());
+				}
+			}
 		}
 	}
 
diff --git a/test/adql/query/TestADQLObjectPosition.java b/test/adql/query/TestADQLObjectPosition.java
index 133caf7cb603d3e460c9c3c2d5bcb2cdc7e38547..772e4072c83f6176ab1abf91fa453d53ad5d039b 100644
--- a/test/adql/query/TestADQLObjectPosition.java
+++ b/test/adql/query/TestADQLObjectPosition.java
@@ -10,6 +10,7 @@ import org.junit.Before;
 import org.junit.Test;
 
 import adql.parser.ADQLParser;
+import adql.parser.ADQLParser.ADQLVersion;
 import adql.parser.grammar.ParseException;
 import adql.query.constraint.Comparison;
 import adql.query.from.ADQLJoin;
@@ -28,24 +29,28 @@ public class TestADQLObjectPosition {
 
 	@Test
 	public void testPositionInAllClauses() {
-		try {
-			ADQLQuery query = new ADQLParser().parseQuery("SELECT truc, bidule.machin, toto(truc, chose) AS \"super\" FROM foo JOIN bidule USING(id) WHERE truc > 12.5 AND bidule.machin < 5 GROUP BY chose HAVING try > 0 ORDER BY chouetteAlors");
+		for(ADQLVersion version : ADQLVersion.values()) {
+			try {
+				ADQLQuery query = new ADQLParser(version).parseQuery("SELECT truc, bidule.machin, toto(truc, chose) AS \"super\" FROM foo JOIN bidule USING(id) WHERE truc > 12.5 AND bidule.machin < 5 GROUP BY chose HAVING try > 0 ORDER BY chouetteAlors, 2 DESC");
 
-			Iterator<ADQLObject> results = query.search(new SimpleSearchHandler(true) {
-				@Override
-				protected boolean match(ADQLObject obj) {
-					return obj.getPosition() == null;
+				Iterator<ADQLObject> results = query.search(new SimpleSearchHandler(true) {
+					@Override
+					protected boolean match(ADQLObject obj) {
+						return obj.getPosition() == null;
+					}
+				});
+				if (results.hasNext()) {
+					System.err.println("OBJECT WITH NO DEFINED POSITION in ADQL-" + version + ":");
+					while(results.hasNext()) {
+						ADQLObject r = results.next();
+						System.err.println("    * " + r.toADQL() + " {" + r.getClass().getSimpleName() + "}");
+					}
+					fail("At least one item of the generated ADQL tree does not have a position information! (see System.err for more details)");
 				}
-			});
-			if (results.hasNext()) {
-				System.err.println("OBJECT WITH NO DEFINED POSITION:");
-				while(results.hasNext())
-					System.err.println("    * " + results.next().toADQL());
-				fail("At least one item of the generated ADQL tree does not have a position information! (see System.err for more details)");
+			} catch(ParseException pe) {
+				pe.printStackTrace();
+				fail("No error should have occured here: the ADQL query is syntactically correct!");
 			}
-		} catch(ParseException pe) {
-			pe.printStackTrace();
-			fail("No error should have occured here: the ADQL query is syntactically correct!");
 		}
 	}
 
@@ -58,80 +63,84 @@ public class TestADQLObjectPosition {
 
 	@Test
 	public void testPositionAccuracy() {
-		try {
-			ADQLQuery query = new ADQLParser().parseQuery("SELECT TOP 1000 oid FROM foo JOIN bar USING(oid)\nWHERE foo || toto = 'truc'\n      AND 2 > 1+0 GROUP BY oid HAVING COUNT(oid) > 10\n\tORDER BY 1 DESC");
-			// Test SELECT
-			assertEquality(new TextPosition(1, 1, 1, 20), query.getSelect().getPosition());
-			// Test ADQLColumn (here: "oid")
-			assertEquality(new TextPosition(1, 17, 1, 20), query.getSelect().get(0).getPosition());
-			// Test FROM & ADQLJoin
-			/* NB: The clause FROM is the only one which is not a list but a single item of type FromContent (JOIN or table).
-			 *     That's why, it is not possible to get its exact starting position ('FROM') ; the starting position is
-			 *     the one of the first table of the clause FROM. */
-			assertEquality(new TextPosition(1, 26, 1, 49), query.getFrom().getPosition());
-			// Test ADQLTable
-			List<ADQLTable> tables = query.getFrom().getTables();
-			assertEquality(new TextPosition(1, 26, 1, 29), tables.get(0).getPosition());
-			assertEquality(new TextPosition(1, 35, 1, 38), tables.get(1).getPosition());
-			// Test the join condition:
-			Iterator<ADQLColumn> itCol = ((ADQLJoin)query.getFrom()).getJoinedColumns();
-			assertEquality(new TextPosition(1, 45, 1, 48), itCol.next().getPosition());
-			// Test WHERE
-			assertEquality(new TextPosition(2, 1, 3, 18), query.getWhere().getPosition());
-			// Test COMPARISON = CONSTRAINT
-			Comparison comp = (Comparison)(query.getWhere().get(0));
-			assertEquality(new TextPosition(2, 7, 2, 27), comp.getPosition());
-			// Test left operand = concatenation:
-			ADQLOperand operand = comp.getLeftOperand();
-			assertEquality(new TextPosition(2, 7, 2, 18), operand.getPosition());
-			Iterator<ADQLObject> itObj = operand.adqlIterator();
-			// foo
-			assertEquality(new TextPosition(2, 7, 2, 10), itObj.next().getPosition());
-			// toto
-			assertEquality(new TextPosition(2, 14, 2, 18), itObj.next().getPosition());
-			// Test right operand = string:
-			operand = comp.getRightOperand();
-			assertEquality(new TextPosition(2, 21, 2, 27), operand.getPosition());
-			// Test COMPARISON > CONSTRAINT:
-			comp = (Comparison)(query.getWhere().get(1));
-			assertEquality(new TextPosition(3, 11, 3, 18), comp.getPosition());
-			// Test left operand = numeric:
-			operand = comp.getLeftOperand();
-			assertEquality(new TextPosition(3, 11, 3, 12), operand.getPosition());
-			// Test right operand = operation:
-			operand = comp.getRightOperand();
-			assertEquality(new TextPosition(3, 15, 3, 18), operand.getPosition());
-			itObj = operand.adqlIterator();
-			// 1
-			assertEquality(new TextPosition(3, 15, 3, 16), itObj.next().getPosition());
-			// 0
-			assertEquality(new TextPosition(3, 17, 3, 18), itObj.next().getPosition());
-			// Test GROUP BY
-			assertEquality(new TextPosition(3, 19, 3, 31), query.getGroupBy().getPosition());
-			// oid
-			assertEquality(new TextPosition(3, 28, 3, 31), query.getGroupBy().get(0).getPosition());
-			// Test HAVING
-			assertEquality(new TextPosition(3, 32, 3, 54), query.getHaving().getPosition());
-			// Test COMPARISON > CONSTRAINT:
-			comp = (Comparison)(query.getHaving().get(0));
-			assertEquality(new TextPosition(3, 39, 3, 54), comp.getPosition());
-			// Test left operand = COUNT function:
-			operand = comp.getLeftOperand();
-			assertEquality(new TextPosition(3, 39, 3, 49), operand.getPosition());
-			// Test parameter = ADQLColumn oid:
-			assertEquality(new TextPosition(3, 45, 3, 48), ((ADQLFunction)operand).getParameter(0).getPosition());
-			// Test right operand = operation:
-			operand = comp.getRightOperand();
-			assertEquality(new TextPosition(3, 52, 3, 54), operand.getPosition());
-			// Test ORDER BY
-			assertEquality(new TextPosition(4, 9, 4, 24), query.getOrderBy().getPosition());
-			// Test column index:
-			assertEquality(new TextPosition(4, 18, 4, 19), query.getOrderBy().get(0).getPosition());
+		for(ADQLVersion version : ADQLVersion.values()) {
+			try {
+				ADQLQuery query = new ADQLParser(version).parseQuery("SELECT TOP 1000 oid FROM foo JOIN bar USING(oid)\nWHERE foo || toto = 'truc'\n      AND 2 > 1+0 GROUP BY oid HAVING COUNT(oid) > 10\n\tORDER BY 1 DESC");
+				// Test SELECT
+				assertEquality(new TextPosition(1, 1, 1, 20), query.getSelect().getPosition());
+				// Test ADQLColumn (here: "oid")
+				assertEquality(new TextPosition(1, 17, 1, 20), query.getSelect().get(0).getPosition());
+				// Test FROM & ADQLJoin
+				/* NB: The clause FROM is the only one which is not a list but a single item of type FromContent (JOIN or table).
+				 *     That's why, it is not possible to get its exact starting position ('FROM') ; the starting position is
+				 *     the one of the first table of the clause FROM. */
+				assertEquality(new TextPosition(1, 26, 1, 49), query.getFrom().getPosition());
+				// Test ADQLTable
+				List<ADQLTable> tables = query.getFrom().getTables();
+				assertEquality(new TextPosition(1, 26, 1, 29), tables.get(0).getPosition());
+				assertEquality(new TextPosition(1, 35, 1, 38), tables.get(1).getPosition());
+				// Test the join condition:
+				Iterator<ADQLColumn> itCol = ((ADQLJoin)query.getFrom()).getJoinedColumns();
+				assertEquality(new TextPosition(1, 45, 1, 48), itCol.next().getPosition());
+				// Test WHERE
+				assertEquality(new TextPosition(2, 1, 3, 18), query.getWhere().getPosition());
+				// Test COMPARISON = CONSTRAINT
+				Comparison comp = (Comparison)(query.getWhere().get(0));
+				assertEquality(new TextPosition(2, 7, 2, 27), comp.getPosition());
+				// Test left operand = concatenation:
+				ADQLOperand operand = comp.getLeftOperand();
+				assertEquality(new TextPosition(2, 7, 2, 18), operand.getPosition());
+				Iterator<ADQLObject> itObj = operand.adqlIterator();
+				// foo
+				assertEquality(new TextPosition(2, 7, 2, 10), itObj.next().getPosition());
+				// toto
+				assertEquality(new TextPosition(2, 14, 2, 18), itObj.next().getPosition());
+				// Test right operand = string:
+				operand = comp.getRightOperand();
+				assertEquality(new TextPosition(2, 21, 2, 27), operand.getPosition());
+				// Test COMPARISON > CONSTRAINT:
+				comp = (Comparison)(query.getWhere().get(1));
+				assertEquality(new TextPosition(3, 11, 3, 18), comp.getPosition());
+				// Test left operand = numeric:
+				operand = comp.getLeftOperand();
+				assertEquality(new TextPosition(3, 11, 3, 12), operand.getPosition());
+				// Test right operand = operation:
+				operand = comp.getRightOperand();
+				assertEquality(new TextPosition(3, 15, 3, 18), operand.getPosition());
+				itObj = operand.adqlIterator();
+				// 1
+				assertEquality(new TextPosition(3, 15, 3, 16), itObj.next().getPosition());
+				// 0
+				assertEquality(new TextPosition(3, 17, 3, 18), itObj.next().getPosition());
+				// Test GROUP BY
+				assertEquality(new TextPosition(3, 19, 3, 31), query.getGroupBy().getPosition());
+				// oid
+				assertEquality(new TextPosition(3, 28, 3, 31), query.getGroupBy().get(0).getPosition());
+				// Test HAVING
+				assertEquality(new TextPosition(3, 32, 3, 54), query.getHaving().getPosition());
+				// Test COMPARISON > CONSTRAINT:
+				comp = (Comparison)(query.getHaving().get(0));
+				assertEquality(new TextPosition(3, 39, 3, 54), comp.getPosition());
+				// Test left operand = COUNT function:
+				operand = comp.getLeftOperand();
+				assertEquality(new TextPosition(3, 39, 3, 49), operand.getPosition());
+				// Test parameter = ADQLColumn oid:
+				assertEquality(new TextPosition(3, 45, 3, 48), ((ADQLFunction)operand).getParameter(0).getPosition());
+				// Test right operand = operation:
+				operand = comp.getRightOperand();
+				assertEquality(new TextPosition(3, 52, 3, 54), operand.getPosition());
+				// Test ORDER BY
+				assertEquality(new TextPosition(4, 9, 4, 24), query.getOrderBy().getPosition());
+				// Test ORDER BY item:
+				assertEquality(new TextPosition(4, 18, 4, 24), query.getOrderBy().get(0).getPosition());
+				// Test column index:
+				assertEquality(new TextPosition(4, 18, 4, 19), query.getOrderBy().get(0).getColumnReference().getPosition());
 
-		} catch(ParseException pe) {
-			System.err.println("ERROR IN THE ADQL QUERY AT " + pe.getPosition());
-			pe.printStackTrace();
-			fail("No error should have occured here: the ADQL query is syntactically correct!");
+			} catch(ParseException pe) {
+				System.err.println("ERROR IN THE ADQL-" + version + " QUERY AT " + pe.getPosition());
+				pe.printStackTrace();
+				fail("No error should have occured here: the ADQL query is syntactically correct!");
+			}
 		}
 	}