diff --git a/identifiers_rules.md b/identifiers_rules.md
new file mode 100644
index 0000000000000000000000000000000000000000..b3219070bc81da9c0c99f11358bc0bbc776dc37c
--- /dev/null
+++ b/identifiers_rules.md
@@ -0,0 +1,218 @@
+_**Date:** 25th Sept. 2019_
+_**Author:** Grégory Mantelet_
+
+# Identifiers' rules in ADQL-Lib
+
+## Definitions
+
+These rules apply to any kind of identifier (e.g. table, column).
+
+To simplify explanations, we will consider here that an identifier is composed of 3 pieces of information:
+
+- adqlName _- string_
+- adqlCaseSensitive _- boolean_
+- dbName _- string_
+
+`adqlName` and `dbName` are never stored in their qualified (e.g. the table prefix inside a full column name) or their delimited (e.g. `"aTable"` is the delimited form of `aTable`) form ; surrounded double quotes (and escaped double quotes: `""`) and prefixes are used/checked at initialization but always discarded for storage.
+
+If `dbName` is not specified, it is the same as `adqlName`.
+
+`adqlCaseSensitive` is set to `true` only if `adqlName` was delimited at initialization.
+
+*Rules about how to build this 3-tuple depend on the origin of the identifier (e.g. `TAP_SCHEMA`, subquery, CTE) ; these rules are detailed below.*
+
+Let's now see how to write ADQL and SQL queries with a such 3-tuple...
+
+## Identifiers in ADQL
+
+In ADQL queries, an identifier MUST be delimited only in the following cases:
+
+* if a ADQL/SQL reserved keyword
+
+  ```sql
+  -- declared table identifier: adqlName=`distance`
+  SELECT ... FROM distance -- INCORRECT because `distance` is a reserved keyword
+  SELECT ... FROM "distance" -- CORRECT
+  ```
+
+* if not a regular ADQL identifier
+
+  ```sql
+  -- declared table identifier: adqlName=`2do`
+  SELECT ... FROM 2do -- INCORRECT because `2do` is starting with a digit
+  SELECT ... FROM "2do" -- CORRECT
+  ```
+
+* if ambiguous with another identifier of the same type
+
+  ```sql
+  -- declared column identifiers: adqlName=`id` in `table1` and adqlName=`id` in `table2`
+  SELECT id FROM table1, table2 -- INCORRECT because the column `id` exists in both tables
+  SELECT table1.id FROM table1, table2 -- CORRECT
+  ```
+
+
+
+In any other case, the identifier _MAY_ be delimited, but if not, you are free to write it in upper-/lower-/mixed-case.
+
+If the identifier is declared in a *CASE-SENSITIVE* way, it MUST be respected when delimited in the ADQL query.
+
+If the identifier is *CASE-INSENSITIVE*, its delimited ADQL version MUST be all in lower-case.
+
+Then, the following ADQL queries are perfectly allowed:
+
+```sql
+-- declared table: adqlName=`aTable`, adqlCaseSensitive=`false`
+SELECT ... FROM atable -- OK
+SELECT ... FROM ATABLE -- OK
+SELECT ... FROM "atable" -- OK (because lower-case if not declared CS)
+SELECT ... FROM "aTable" -- INCORRECT
+
+-- declared table: adqlName=`aTable`, adqlCaseSensitive=`true`
+SELECT ... FROM atable -- OK
+SELECT ... FROM ATABLE -- OK
+SELECT ... FROM "atable" -- INCORRECT
+SELECT ... FROM "aTable" -- OK
+```
+
+## SQL translation
+
+_In this part, we will consider PostgreSQL as SQL target._
+
+The `dbName` of an identifier is _always_ considered as _case-sensitive_. So, it will _always_ be written delimited in SQL queries.
+
+**Examples of SQL queries:**
+
+```sql
+-- declared table: adqlName=`aTable`, dbName=-
+-- ADQL: SELECT ... FROM atable
+SELECT ... FROM "aTable"
+
+-- declare table: adqlName=`aTable`, dbName=`DBTable`
+-- ADQL: SELECT ... FROM atable
+SELECT ... FROM "DBTable"
+```
+
+## Automatic column aliases in SQL
+
+To ensure having the expected labels in SQL query results, aliases are automatically added (if none is specified) to all items of the `SELECT` clause.
+
+As `dbName`s, these default aliases are considered as case sensitive.
+
+They are built using the `adqlName` of the aliased identifiers. If this name is _not case-sensitive_, the alias will be written in lower-case. But if _case-sensitive_, it is written exactly the same.
+
+**Examples of SQL queries:**
+
+```sql
+-- declared table: adqlName=`aTable`, dbName=`DBTable`
+-- declared columns in `aTable`:
+--   * adqlName=`ColCS`, adqlCaseSensitive=`false`, dbName=`dbCol1`
+--   * adqlName=`ColCI`, adqlCaseSensitive=`true`, dbName=`dbCol2`
+-- ADQL: SELECT colcs, colci FROM atable
+SELECT "dbCol1" AS "colcs", "dbCol2" AS "ColCI" FROM "DBTable"
+```
+
+
+
+## Schemas/Tables/Columns declared in `TAP_SCHEMA`
+
+* `adqlName` = `TAP_SCHEMA.(schemas.schema_name|tables.table_name|columns.column_name)`
+* `dbName` = `TAP_SCHEMA.(schemas|tables|columns).dbName` or if NULL `adqlName`
+
+**Examples with `TAP_SCHEMA.tables`:**
+
+| In TAP_SCHEMA.tables                           | In ADQL-Lib                                                  |
+| ---------------------------------------------- | ------------------------------------------------------------ |
+| table_name=`aTable`, dbName=_null_             | adqlName=`aTable`, adqlCaseSensitive=`false`, dbName=_null_  |
+| table_name=`schema.aTable`, dbName=_null_      | adqlName=`aTable`, adqlCaseSensitive=`false`, dbName=_null_  |
+| table_name=`"aTable"`, dbName=_null_           | adqlName=`aTable`, adqlCaseSensitive=`true`, dbName=_null_   |
+| table_name=`schema."aTable"`, dbName=_null_    | adqlName=`aTable`, adqlCaseSensitive=`true`, dbName=_null_   |
+| table_name=`aTable`, dbName=`DBTable`          | adqlName=`aTable`, adqlCaseSensitive=`false`, dbName=`DBTable` |
+| table_name=`aTable`, dbName=`"DBTable"`        | adqlName=`aTable`, adqlCaseSensitive=`false`, dbName=`DBTable` |
+| table_name=`aTable`, dbName=`schema.DBTable`   | adqlName=`aTable`, adqlCaseSensitive=`false`, dbName=`schema.DBTable` |
+| table_name=`aTable`, dbName=`schema."DBTable"` | adqlName=`aTable`, adqlCaseSensitive=`false`, dbName=`schema."DBTable"` |
+
+## Tables from subqueries (i.e. `FROM` and `WITH`)
+
+_Reminder: in ADQL, a subquery declared as table in the `FROM` clause and a CTE declared in the `WITH` clause MUST always be aliased/named._
+
+* `adqlName` = _subquery's alias/CTE's name_
+* `dbName` = `adqlName`
+
+If the alias/name is *delimited* (i.e. case sensitive), `adqlCaseSensitive` will be set to `true` and surrounding double quotes are removed from `adqlName` .
+
+If the alias/name is *not delimited*, `adqlName` is set to the alias put into lower-case, and `adqlCaseSensitive` is `false`.
+
+**Examples:**
+
+```sql
+--
+-- Subqueries in FROM:
+--
+SELECT ... FROM (SELECT * FROM atable) AS t1
+SELECT ... FROM (SELECT * FROM atable) AS T1
+-- => adqlName=`t1`, adqlCaseSensitive=`false`, dbName=`t1`
+
+SELECT ... FROM (SELECT * FROM atable) AS "T1"
+-- => adqlName=`T1`, adqlCaseSensitive=`true`, dbName=`T1`
+
+--
+-- CTEs in WITH:
+--
+WITH t1 AS (SELECT * FROM atable) SELECT ... FROM t1
+WITH T1 AS (SELECT * FROM atable) SELECT ... FROM t1
+-- => adqlName=`t1`, adqlCaseSensitive=`false`, dbName=`t1`
+
+WITH "T1" AS (SELECT * FROM atable) SELECT ... FROM t1
+-- => adqlName=`T1`, adqlCaseSensitive=`true`, dbName=`T1`
+```
+
+## Columns of a (sub)query
+
+* If _NOT aliased_:
+
+  * `adqlName` = _original's `adqlName`_
+  * `adqlCaseSensitive` = _original's `adqlCaseSensitive`_
+  * `dbName` = _original's `dbName`_
+
+  
+
+* If _aliased_:
+
+  * `adqlName` = _alias in lower-case if not delimited, exact same alias otherwise_
+  * `adqlCaseSensitive` = `true` _if alias is delimited_, `false` _otherwise_
+  * `dbName` = `adqlName`
+
+
+
+**Examples:**
+
+```sql
+-- declared column: adqlName=`aColumn`, adqlCaseSensitive=`false`, dbName=`DBCol`
+SELECT acolumn FROM atable
+SELECT AColumn FROM atable
+-- => the declared column
+
+-- declared column: adqlName=`aColumn`, adqlCaseSensitive=`true`, dbName=`DBCol`
+SELECT acolumn FROM atable
+SELECT AColumn FROM atable
+-- => the declared column
+
+-- declared column: adqlName=`aColumn`, adqlCaseSensitive=`true`, dbName=`DBCol`
+SELECT acolumn AS myColumn FROM atable
+-- => adqlName=`mycolumn`, adqlCaseSensitive=`false`, dbName=`mycolumn`
+SELECT acolumn AS "myColumn" FROM atable
+-- => adqlName=`myColumn`, adqlCaseSensitive=`true`, dbName=`myColumn`
+
+```
+
+_**Note:** The new representation of an aliased column has different ADQL and DB names, but the other metadata (e.g. datatype, UCD, ...) of the original column are copied as such._
+
+## Duplicated output columns
+
+_The term 'output columns' means here the columns written in the output format (e.g. VOTable). They are not the columns represented as a 3-tuple in this document._
+
+**TODO**
+
+
+
diff --git a/src/adql/db/CheckContext.java b/src/adql/db/CheckContext.java
new file mode 100644
index 0000000000000000000000000000000000000000..977773f17a928fa98d646166893eea480ea0a4d5
--- /dev/null
+++ b/src/adql/db/CheckContext.java
@@ -0,0 +1,65 @@
+package adql.db;
+
+/*
+ * This file is part of ADQLLibrary.
+ *
+ * ADQLLibrary is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * ADQLLibrary is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * 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 2019 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ */
+
+/**
+ * State of the {@link DBChecker} at one recursion level inside an ADQL query.
+ *
+ * <p>
+ * 	An instance of this class aims to list columns and Common Table Expressions
+ * 	(i.e. CTE - temporary tables defined in the WITH clause) available inside
+ * 	a specific ADQL (sub-)query.
+ * </p>
+ *
+ * @author Gr&eacute;gory Mantelet (CDS)
+ * @version 2.0 (10/2019)
+ * @since 2.0
+ */
+public class CheckContext {
+
+	/** List of available CTEs at this level. */
+	public final SearchTableApi cteTables;
+
+	/** List of available columns (of all tables). */
+	public final SearchColumnList availableColumns;
+
+	/**
+	 * Create a context with the given list of CTEs and columns.
+	 *
+	 * @param cteTables	All available CTEs.
+	 *                 	<i>Replaced by an empty list, if NULL.</i>
+	 * @param columns	All available columns.
+	 *                 	<i>Replaced by an empty list, if NULL.</i>
+	 */
+	public CheckContext(final SearchTableApi cteTables, final SearchColumnList columns) {
+		this.cteTables = (cteTables == null ? new SearchTableList() : cteTables);
+		this.availableColumns = (columns == null ? new SearchColumnList() : columns);
+	}
+
+	/**
+	 * Create a deep copy of this context.
+	 *
+	 * @return	Deep copy.
+	 */
+	public CheckContext getCopy() {
+		return new CheckContext(cteTables.getCopy(), new SearchColumnList(availableColumns));
+	}
+
+}
diff --git a/src/adql/db/DBChecker.java b/src/adql/db/DBChecker.java
index b83e7fefc3f4f2a37481ef165d4c08e765bcd153..9bbb80fa368365ad63cadf1b8beccf18db4570f0 100644
--- a/src/adql/db/DBChecker.java
+++ b/src/adql/db/DBChecker.java
@@ -25,7 +25,6 @@ import java.lang.reflect.InvocationTargetException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -50,6 +49,7 @@ import adql.query.ColumnReference;
 import adql.query.IdentifierField;
 import adql.query.SelectAllColumns;
 import adql.query.SelectItem;
+import adql.query.WithItem;
 import adql.query.from.ADQLTable;
 import adql.query.from.FromContent;
 import adql.query.operand.ADQLColumn;
@@ -71,14 +71,14 @@ import adql.search.SimpleReplaceHandler;
 import adql.search.SimpleSearchHandler;
 
 /**
+ * <h3>ADQL Query verification</h3>
+ *
  * This {@link QueryChecker} implementation is able to do the following
  * verifications on an ADQL query:
  * <ol>
- * 	<li>Check the existence of all table and column references found in a
- * 		query</li>
- * 	<li>Resolve all unknown functions as supported User Defined Functions
- * 		(UDFs)</li>
- * 	<li>Check that types of columns and UDFs match with their context</li>
+ * 	<li>Check existence of all table and column references,</li>
+ * 	<li>Resolve User Defined Functions (UDFs),</li>
+ * 	<li>Check types of columns and UDFs.</li>
  * </ol>
  *
  * <p><i><b>IMPORTANT note:</b>
@@ -90,21 +90,16 @@ import adql.search.SimpleSearchHandler;
  * 	(see also {@link adql.parser.feature.FeatureSet FeatureSet}).
  * </i></p>
  *
- * <h3>Check tables and columns</h3>
+ * <h3>DB content annotations</h3>
+ *
  * <p>
  * 	In addition to check the existence of tables and columns referenced in the
- * 	query, this checked will also attach database metadata on these references
- * 	({@link ADQLTable} and {@link ADQLColumn} instances when they are resolved.
+ * 	query, database metadata ({@link DBTable} or {@link DBColumn}) will also be
+ * 	attached to ({@link ADQLTable} and {@link ADQLColumn} instances when they
+ * 	are resolved.
  * </p>
  *
- * <p>These information are:</p>
- * <ul>
- * 	<li>the corresponding {@link DBTable} or {@link DBColumn} (see getter and
- * 		setter for DBLink in {@link ADQLTable} and {@link ADQLColumn})</li>
- * 	<li>the link between an {@link ADQLColumn} and its {@link ADQLTable}</li>
- * </ul>
- *
- * <p><i><u>Note:</u>
+ * <p><i><b>Note:</b>
  * 	Knowing DB metadata of {@link ADQLTable} and {@link ADQLColumn} is
  * 	particularly useful for the translation of the ADQL query to SQL, because
  * 	the ADQL name of columns and tables can be replaced in SQL by their DB name,
@@ -113,77 +108,42 @@ import adql.search.SimpleSearchHandler;
  * </i></p>
  *
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 2.0 (08/2019)
+ * @version 2.0 (09/2019)
  */
 public class DBChecker implements QueryChecker {
 
-	/** List of all available tables ({@link DBTable}). */
+	/** List of all available tables ({@link DBTable}).
+	 * <p><i><b>IMPORTANT: List shared with all threads.</b>
+	 * 	This list must list all the tables in common to any ADQL query. It
+	 * 	must never contain any temporary table (e.g. uploads).
+	 * </i></p> */
 	protected SearchTableApi lstTables;
 
-	/** List of all allowed geometrical functions (i.e. CONTAINS, REGION, POINT,
-	 * COORD2, ...).
-	 *
-	 * <p>
-	 * 	If this list is NULL, all geometrical functions are allowed.
-	 * 	However, if not, all items of this list must be the only allowed
-	 * 	geometrical functions. So, if the list is empty, no such function is
-	 * 	allowed.
-	 * </p>
-	 *
-	 * @since 1.3
-	 * @deprecated Since v2.0, supported geometrical functions must be declared
-	 *             in ADQLParser. */
-	@Deprecated
-	protected String[] allowedGeo = null;
-
-	/** <p>List of all allowed coordinate systems.</p>
-	 * <p>
-	 * 	Each item of this list must be of the form: "{frame} {refpos} {flavor}".
-	 * 	Each of these 3 items can be either of value, a list of values expressed with the syntax "({value1}|{value2}|...)"
-	 * 	or a '*' to mean all possible values.
-	 * </p>
-	 * <p><i>Note: since a default value (corresponding to the empty string - '') should always be possible for each part of a coordinate system,
-	 * the checker will always add the default value (UNKNOWNFRAME, UNKNOWNREFPOS or SPHERICAL2) into the given list of possible values for each coord. sys. part.</i></p>
-	 * <p>
-	 * 	If this list is NULL, all coordinates systems are allowed.
-	 * 	However, if not, all items of this list must be the only allowed coordinate systems.
-	 * 	So, if the list is empty, none is allowed.
-	 * </p>
-	 * @since 1.3
-	 * @deprecated Since v2.0, supported coordinate systems must be declared
-	 *             in ADQLParser. */
-	@Deprecated
-	protected String[] allowedCoordSys = null;
-
-	/** <p>A regular expression built using the list of allowed coordinate systems.
-	 * With this regex, it is possible to known whether a coordinate system expression is allowed or not.</p>
-	 * <p>If NULL, all coordinate systems are allowed.</p>
-	 * @since 1.3
-	 * @deprecated Since v2.0, supported coordinate systems must be declared
-	 *             in ADQLParser. */
-	@Deprecated
-	protected String coordSysRegExp = null;
-
-	/** <p>List of all allowed User Defined Functions (UDFs).</p>
-	 * <p>
+	/** List of all allowed User Defined Functions (UDFs).
+	 * <p><i><b>Note:</b>
 	 * 	If this list is NULL, any encountered UDF will be allowed.
 	 * 	However, if not, all items of this list must be the only allowed UDFs.
 	 * 	So, if the list is empty, no UDF is allowed.
-	 * </p>
+	 * </i></p>
+	 * <p><i><b>IMPORTANT: List shared with all threads.</b></i></p>
 	 * @since 1.3 */
 	protected FunctionDef[] allowedUdfs = null;
 
-	/* ************ */
-	/* CONSTRUCTORS */
-	/* ************ */
+	/* **********************************************************************
+	   *                          CONSTRUCTORS                              *
+	   ********************************************************************** */
+
 	/**
-	 * <p>Builds a {@link DBChecker} with an empty list of tables.</p>
+	 * Builds a {@link DBChecker} with an empty list of tables.
 	 *
 	 * <p>Verifications done by this object after creation:</p>
 	 * <ul>
-	 * 	<li>Existence of tables and columns:            <b>NO <i>(even unknown or fake tables and columns are allowed)</i></b></li>
-	 * 	<li>Existence of User Defined Functions (UDFs): <b>NO <i>(any "unknown" function is allowed)</i></b></li>
-	 * 	<li>Support of coordinate systems:              <b>NO <i>(all valid coordinate systems are allowed)</i></b></li>
+	 * 	<li>Existence of tables and columns:
+	 * 		<b>NO <i>(even unknown or fake tables and columns are allowed)</i></b></li>
+	 * 	<li>Existence of User Defined Functions (UDFs):
+	 * 		<b>NO <i>(any "unknown" function is allowed)</i></b></li>
+	 * 	<li>Types consistency:
+	 * 		<b>NO</b></li>
 	 * </ul>
 	 */
 	public DBChecker() {
@@ -191,13 +151,16 @@ public class DBChecker implements QueryChecker {
 	}
 
 	/**
-	 * <p>Builds a {@link DBChecker} with the given list of known tables.</p>
+	 * Builds a {@link DBChecker} with the given list of known tables.
 	 *
 	 * <p>Verifications done by this object after creation:</p>
 	 * <ul>
-	 * 	<li>Existence of tables and columns:            <b>OK</b></li>
-	 * 	<li>Existence of User Defined Functions (UDFs): <b>NO <i>(any "unknown" function is allowed)</i></b></li>
-	 * 	<li>Support of coordinate systems:              <b>NO <i>(all valid coordinate systems are allowed)</i></b></li>
+	 * 	<li>Existence of tables and columns:
+	 * 		<b>OK</b></li>
+	 * 	<li>Existence of User Defined Functions (UDFs):
+	 * 		<b>NO <i>(any "unknown" function is allowed)</i></b></li>
+	 * 	<li>Types consistency:
+	 * 		<b>OK, except with unknown functions</b></li>
 	 * </ul>
 	 *
 	 * @param tables	List of all available tables.
@@ -207,20 +170,26 @@ public class DBChecker implements QueryChecker {
 	}
 
 	/**
-	 * <p>Builds a {@link DBChecker} with the given list of known tables and with a restricted list of user defined functions.</p>
+	 * Builds a {@link DBChecker} with the given list of known tables and with a
+	 * restricted list of User Defined Functions (UDFs).
 	 *
 	 * <p>Verifications done by this object after creation:</p>
 	 * <ul>
-	 * 	<li>Existence of tables and columns:            <b>OK</b></li>
-	 * 	<li>Existence of User Defined Functions (UDFs): <b>OK</b></li>
-	 * 	<li>Support of coordinate systems:              <b>NO <i>(all valid coordinate systems are allowed)</i></b></li>
+	 * 	<li>Existence of tables and columns:
+	 * 		<b>OK</b></li>
+	 * 	<li>Existence of User Defined Functions (UDFs):
+	 * 		<b>OK</b></li>
+	 * 	<li>Types consistency:
+	 * 		<b>OK</b></li>
 	 * </ul>
 	 *
 	 * @param tables		List of all available tables.
 	 * @param allowedUdfs	List of all allowed user defined functions.
-	 *                   	If NULL, no verification will be done (and so, all UDFs are allowed).
+	 *                   	If NULL, no verification will be done (and so, all
+	 *                   	UDFs are allowed).
 	 *                   	If empty list, no "unknown" (or UDF) is allowed.
-	 *                   	<i>Note: match with items of this list are done case insensitively.</i>
+	 *                   	<i><b>Note:</b> match with items of this list are
+	 *                   	done case insensitively.</i>
 	 *
 	 * @since 1.3
 	 */
@@ -250,144 +219,17 @@ public class DBChecker implements QueryChecker {
 		}
 	}
 
+	/* **********************************************************************
+	   *                             SETTERS                                *
+	   ********************************************************************** */
 	/**
-	 * <p>Builds a {@link DBChecker} with the given list of known tables and with a restricted list of user defined functions.</p>
-	 *
-	 * <p>Verifications done by this object after creation:</p>
-	 * <ul>
-	 * 	<li>Existence of tables and columns:            <b>OK</b></li>
-	 * 	<li>Existence of User Defined Functions (UDFs): <b>NO <i>(any "unknown" function is allowed)</i></b></li>
-	 * 	<li>Support of geometrical functions:           <b>OK</b></li>
-	 * 	<li>Support of coordinate systems:              <b>OK</b></li>
-	 * </ul>
-	 *
-	 * @param tables			List of all available tables.
-	 * @param allowedGeoFcts	List of all allowed geometrical functions (i.e. CONTAINS, POINT, UNION, CIRCLE, COORD1).
-	 *                      	If NULL, no verification will be done (and so, all geometries are allowed).
-	 *                      	If empty list, no geometry function is allowed.
-	 *                      	<i>Note: match with items of this list are done case insensitively.</i>
-	 * @param allowedCoordSys	List of all allowed coordinate system patterns. The syntax of a such pattern is the following:
-	 *                       	"{frame} {refpos} {flavor}" ; on the contrary to a coordinate system expression, here no part is optional.
-	 *                       	Each part of this pattern can be one the possible values (case insensitive), a list of possible values
-	 *                       	expressed with the syntax "({value1}|{value2}|...)", or a '*' for any valid value.
-	 *                       	For instance: "ICRS (GEOCENTER|heliocenter) *".
-	 *                       	If the given list is NULL, no verification will be done (and so, all coordinate systems are allowed).
-	 *                       	If it is empty, no coordinate system is allowed (except the default values - generally expressed by an empty string: '').
-	 *
-	 * @since 1.3
-	 * @deprecated	Since v2.0, the check of geometrical functions support is
-	 *            	performed in ADQLParser. It must now be done with
-	 *            	{@link adql.parser.ADQLParser#getSupportedFeatures() ADQLParser.getSupportedFeatures()}
-	 * 	          	(see also {@link adql.parser.feature.FeatureSet FeatureSet}).
-	 */
-	@Deprecated
-	public DBChecker(final Collection<? extends DBTable> tables, final Collection<String> allowedGeoFcts, final Collection<String> allowedCoordSys) throws ParseException {
-		this(tables, null, allowedGeoFcts, allowedCoordSys);
-	}
-
-	/**
-	 * <p>Builds a {@link DBChecker}.</p>
-	 *
-	 * <p>Verifications done by this object after creation:</p>
-	 * <ul>
-	 * 	<li>Existence of tables and columns:            <b>OK</b></li>
-	 * 	<li>Existence of User Defined Functions (UDFs): <b>OK</b></li>
-	 * 	<li>Support of coordinate systems:              <b>OK</b></li>
-	 * </ul>
-	 *
-	 * <p><i><b>IMPORTANT note:</b>
-	 * 	Since v2.0, the check of supported geometrical functions is performed
-	 * 	directly in ADQLParser through the notion of Optional Features.
-	 * 	The declaration of supported geometrical functions must now be done
-	 * 	with {@link adql.parser.ADQLParser#getSupportedFeatures() ADQLParser.getSupportedFeatures()}
-	 * 	(see also {@link adql.parser.feature.FeatureSet FeatureSet}).
-	 * </i></p>
-	 *
-	 * @param tables			List of all available tables.
-	 * @param allowedUdfs		List of all allowed user defined functions.
-	 *                   		If NULL, no verification will be done (and so, all UDFs are allowed).
-	 *                   		If empty list, no "unknown" (or UDF) is allowed.
-	 *                   		<i>Note: match with items of this list are done case insensitively.</i>
-	 * @param allowedGeoFcts	List of all allowed geometrical functions (i.e. CONTAINS, POINT, UNION, CIRCLE, COORD1).
-	 *                      	If NULL, no verification will be done (and so, all geometries are allowed).
-	 *                      	If empty list, no geometry function is allowed.
-	 *                      	<i>Note: match with items of this list are done case insensitively.</i>
-	 * @param allowedCoordSys	List of all allowed coordinate system patterns. The syntax of a such pattern is the following:
-	 *                       	"{frame} {refpos} {flavor}" ; on the contrary to a coordinate system expression, here no part is optional.
-	 *                       	Each part of this pattern can be one the possible values (case insensitive), a list of possible values
-	 *                       	expressed with the syntax "({value1}|{value2}|...)", or a '*' for any valid value.
-	 *                       	For instance: "ICRS (GEOCENTER|heliocenter) *".
-	 *                       	If the given list is NULL, no verification will be done (and so, all coordinate systems are allowed).
-	 *                       	If it is empty, no coordinate system is allowed (except the default values - generally expressed by an empty string: '').
-	 *
-	 * @since 2.0
-	 * @deprecated	Since v2.0, the check of geometrical functions support is
-	 *            	performed in ADQLParser. It must now be done with
-	 *            	{@link adql.parser.ADQLParser#getSupportedFeatures() ADQLParser.getSupportedFeatures()}
-	 * 	          	(see also {@link adql.parser.feature.FeatureSet FeatureSet}).
-	 */
-	@Deprecated
-	public DBChecker(final Collection<? extends DBTable> tables, final Collection<? extends FunctionDef> allowedUdfs, final Collection<String> allowedGeoFcts, final Collection<String> allowedCoordSys) throws ParseException {
-		// Set the list of available tables + Set the list of all known UDFs:
-		this(tables, allowedUdfs);
-
-		// Set the list of allowed geometrical functions:
-		allowedGeo = specialSort(allowedGeoFcts);
-
-		// Set the list of allowed coordinate systems:
-		this.allowedCoordSys = specialSort(allowedCoordSys);
-		coordSysRegExp = STCS.buildCoordSysRegExp(this.allowedCoordSys);
-	}
-
-	/**
-	 * Transform the given collection of string elements in a sorted array.
-	 * Only non-NULL and non-empty strings are kept.
-	 *
-	 * @param items	Items to copy and sort.
-	 *
-	 * @return	A sorted array containing all - except NULL and empty strings - items of the given collection.
-	 *
-	 * @since 1.3
-	 *
-	 * @deprecated	Since v2.0, this tool function is no longer used. It was
-	 *            	useful only to collect allowed geometries and coordinate
-	 *            	systems....but these are now checked by
-	 *            	{@link adql.parser.ADQLParser ADQLParser}.
-	 */
-	@Deprecated
-	protected final static String[] specialSort(final Collection<String> items) {
-		// Nothing to do if the array is NULL:
-		if (items == null)
-			return null;
-
-		// Keep only valid items (not NULL and not empty string):
-		String[] tmp = new String[items.size()];
-		int cnt = 0;
-		for(String item : items) {
-			if (item != null && item.trim().length() > 0)
-				tmp[cnt++] = item;
-		}
-
-		// Make an adjusted array copy:
-		String[] copy = new String[cnt];
-		System.arraycopy(tmp, 0, copy, 0, cnt);
-
-		// Sort the values:
-		Arrays.sort(copy);
-
-		return copy;
-	}
-
-	/* ****** */
-	/* SETTER */
-	/* ****** */
-	/**
-	 * <p>Sets the list of all available tables.</p>
+	 * Sets the list of all available tables.
 	 *
-	 * <p><i><u>Note:</u>
-	 * 	Only if the given collection is NOT an implementation of
-	 * 	{@link SearchTableApi}, the collection will be copied inside a new
-	 * 	{@link SearchTableList}, otherwise it is used as provided.
+	 * <p><i><b>Note:</b>
+	 * 	Only if the given collection is an implementation of
+	 * 	{@link SearchTableApi}, it will be used directly as provided.
+	 * 	Otherwise the given collection will be copied inside a new
+	 * 	{@link SearchTableList}.
 	 * </i></p>
 	 *
 	 * @param tables	List of {@link DBTable}s.
@@ -401,16 +243,17 @@ public class DBChecker implements QueryChecker {
 			lstTables = new SearchTableList(tables);
 	}
 
-	/* ************* */
-	/* CHECK METHODS */
-	/* ************* */
+	/* **********************************************************************
+	   *                       CHECK METHODS                                *
+	   ********************************************************************** */
+
 	/**
-	 * Check all the columns, tables and UDFs references inside the given query.
+	 * Check all the column, table and UDF references inside the given query.
 	 *
 	 * <p><i><b>Note:</b>
 	 * 	This query has already been parsed ; thus it is already syntactically
 	 * 	correct. Only the consistency with the published tables, columns and all
-	 * 	the defined UDFs must be checked.
+	 * 	the defined UDFs will be checked.
 	 * </i></p>
 	 *
 	 * @param query		The query to check.
@@ -439,12 +282,9 @@ public class DBChecker implements QueryChecker {
 	 * </ol>
 	 *
 	 * @param query			The query to check.
-	 * @param fathersList	List of all columns available in the father queries
-	 *                   	and that should be accessed in sub-queries. 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 contextList	Each item of this stack represents a recursion level
+	 *                   	inside the main ADQL query. A such item contains the
+	 *                   	list of columns and tables available at this level.
 	 *
 	 * @throws UnresolvedIdentifiersException	An {@link UnresolvedIdentifiersException}
 	 *                                       	if one or several of the above
@@ -459,11 +299,19 @@ public class DBChecker implements QueryChecker {
 	 * @see #checkUDFs(ADQLQuery, UnresolvedIdentifiersException)
 	 * @see #checkTypes(ADQLQuery, UnresolvedIdentifiersException)
 	 */
-	protected void check(final ADQLQuery query, final Stack<SearchColumnList> fathersList) throws UnresolvedIdentifiersException {
+	protected void check(final ADQLQuery query, Stack<CheckContext> contextList) throws UnresolvedIdentifiersException {
 		UnresolvedIdentifiersException errors = new UnresolvedIdentifiersException();
 
+		// Initialize the context:
+		if (contextList == null)
+			contextList = new Stack<CheckContext>();
+		if (contextList.isEmpty())
+			contextList.push(new CheckContext(null, null));
+		else
+			contextList.push(contextList.peek().getCopy());
+
 		// A. Check DB items (tables and columns):
-		SearchColumnList availableColumns = checkDBItems(query, fathersList, errors);
+		checkDBItems(query, contextList, errors);
 
 		// B. Check UDFs:
 		if (allowedUdfs != null)
@@ -473,44 +321,50 @@ public class DBChecker implements QueryChecker {
 		checkTypes(query, errors);
 
 		// D. Check sub-queries:
-		checkSubQueries(query, fathersList, availableColumns, errors);
+		checkSubQueries(query, contextList, errors);
 
 		// Throw all errors, if any:
 		if (errors.getNbErrors() > 0)
 			throw errors;
+
+		// Remove the current context:
+		contextList.pop();
 	}
 
-	/* ************************************************ */
-	/* CHECKING METHODS FOR DB ITEMS (TABLES & COLUMNS) */
-	/* ************************************************ */
+	/* **********************************************************************
+	   *          CHECKING METHODS FOR DB ITEMS (TABLES & COLUMNS)          *
+	   ********************************************************************** */
 
 	/**
-	 * <p>Check DB items (tables and columns) used in the given ADQL query.</p>
+	 * Check DB items (tables and columns) used in the given ADQL query.
 	 *
 	 * <p>Operations done in this function:</p>
 	 * <ol>
 	 * 	<li>Resolve all found tables</li>
-	 * 	<li>Get the whole list of all available columns <i>Note: this list is returned by this function.</i></li>
+	 * 	<li>Get the whole list of all available columns. <i>(<b>note:</b> this list is
+	 * 		returned by this function)</i></li>
 	 * 	<li>Resolve all found columns</li>
 	 * </ol>
 	 *
-	 * @param query			Query in which the existence of DB items 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 errors		List of errors to complete in this function each time an unknown table or column is encountered.
+	 * @param query			Query in which the existence of DB items must be
+	 *             			checked.
+	 * @param contextList	Each item of this stack represents a recursion level
+	 *                   	inside the main ADQL query. A such item contains the
+	 *                   	list of columns and tables available at this level.
+	 * @param errors		List of errors to complete in this function each
+	 *              		time an unknown table or column is encountered.
 	 *
 	 * @return	List of all columns available in the given query.
 	 *
 	 * @see #resolveTables(ADQLQuery, Stack, UnresolvedIdentifiersException)
 	 * @see FromContent#getDBColumns()
-	 * @see #resolveColumns(ADQLQuery, Stack, Map, SearchColumnList, UnresolvedIdentifiersException)
+	 * @see #resolveColumns(ADQLQuery, Stack, UnresolvedIdentifiersException)
 	 *
 	 * @since 1.3
 	 */
-	protected SearchColumnList checkDBItems(final ADQLQuery query, final Stack<SearchColumnList> fathersList, final UnresolvedIdentifiersException errors) {
+	protected SearchColumnList checkDBItems(final ADQLQuery query, final Stack<CheckContext> contextList, final UnresolvedIdentifiersException errors) {
 		// a. Resolve all tables:
-		Map<DBTable, ADQLTable> mapTables = resolveTables(query, fathersList, errors);
+		resolveTables(query, contextList, errors);
 
 		// b. Get the list of all columns made available in the clause FROM:
 		SearchColumnList availableColumns;
@@ -520,9 +374,10 @@ public class DBChecker implements QueryChecker {
 			errors.addException(pe);
 			availableColumns = new SearchColumnList();
 		}
+		contextList.peek().availableColumns.addAll(availableColumns);
 
 		// c. Resolve all columns:
-		resolveColumns(query, fathersList, mapTables, availableColumns, errors);
+		resolveColumns(query, contextList, errors);
 
 		return availableColumns;
 	}
@@ -532,16 +387,24 @@ public class DBChecker implements QueryChecker {
 	 * the available tables, and if there is only one match, attach the matching
 	 * metadata to them.
 	 *
-	 * <b>Management of sub-query tables</b>
+	 * <h3>Management of Common Table Expressions (CTEs ; WITH expressions)</h3>
+	 * <p>
+	 * 	If the clause WITH is not empty, any declared CTE/sub-query will be
+	 * 	checked. If correct, a {@link DBTable} will be generated using
+	 * 	{@link #generateDBTable(WithItem)} representing this sub-query. This
+	 * 	{@link DBTable} is immediately added to the current context so that
+	 * 	being referenced in the main query.
+	 * </p>
+	 *
+	 * <h3>Management of sub-query tables</h3>
 	 * <p>
 	 * 	If a table is not a DB table reference but a sub-query, this latter is
-	 * 	first checked (using {@link #check(ADQLQuery, Stack)} ; but the father
-	 * 	list must not contain tables of the given query, because on the same
-	 * 	level) and then corresponding table metadata are generated (using
+	 * 	first checked, using {@link #check(ADQLQuery, Stack)}. Then, its
+	 * 	corresponding table metadata are generated (using
 	 * 	{@link #generateDBTable(ADQLQuery, String)}) and attached to it.
 	 * </p>
 	 *
-	 * <b>Management of "{table}.*" in the SELECT clause</b>
+	 * <h3>Management of "{table}.*" in the SELECT clause</h3>
 	 * <p>
 	 * 	For each of this SELECT item, this function tries to resolve the table
 	 * 	name. If only one match is found, the corresponding ADQL table object
@@ -550,7 +413,7 @@ public class DBChecker implements QueryChecker {
 	 * 	the referenced table is a sub-query).
 	 * </p>
 	 *
-	 * <b>Table alias</b>
+	 * <h3>Table alias</h3>
 	 * <p>
 	 * 	When a simple table (i.e. not a sub-query) is aliased, the metadata of
 	 * 	this table will be wrapped inside a {@link DBTableAlias} in order to
@@ -567,22 +430,52 @@ public class DBChecker implements QueryChecker {
 	 *
 	 * @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 contextList	Each item of this stack represents a recursion level
+	 *                   	inside the main ADQL query. A such item contains the
+	 *                   	list of columns and tables available at this level.
 	 * @param errors		List of errors to complete in this function each
 	 *              		time an unknown table or column is encountered.
-	 *
-	 * @return	An associative map of all the resolved tables.
 	 */
-	protected Map<DBTable, ADQLTable> resolveTables(final ADQLQuery query, final Stack<SearchColumnList> fathersList, final UnresolvedIdentifiersException errors) {
-		HashMap<DBTable, ADQLTable> mapTables = new HashMap<DBTable, ADQLTable>();
+	protected void resolveTables(final ADQLQuery query, final Stack<CheckContext> contextList, final UnresolvedIdentifiersException errors) {
+		final CheckContext context = contextList.peek();
 		ISearchHandler sHandler;
 
-		// Check the existence of all tables:
+		// Resolve tables/queries declared in the WITH clause:
+		ADQLTable[] declaredCTEs = new ADQLTable[query.getWith().size()];
+		int i = 0;
+		for(WithItem withItem : query.getWith()) {
+
+			// Check this query (and set all the metadata on all DB items)
+			try {
+				check(withItem.getQuery(), contextList);
+			} catch(UnresolvedIdentifiersException uie) {
+				for(ParseException pe : uie)
+					errors.addException(pe);
+			}
+
+			// Generate the corresponding DBTable:
+			withItem.setDBLink(generateDBTable(withItem));
+
+			// Build a corresponding virtual ADQLTable:
+			ADQLTable adqlTable = new ADQLTable(null, withItem.getLabel());
+			adqlTable.setCaseSensitive(IdentifierField.TABLE, withItem.isLabelCaseSensitive());
+			adqlTable.setDBLink(withItem.getDBLink());
+			declaredCTEs[i++] = adqlTable;
+
+			// Update the context:
+			context.cteTables.add(adqlTable.getDBLink());
+
+			// Check the number of columns:
+			if (withItem.getColumnLabels() != null) {
+				DBColumn[] columns = withItem.getResultingColumns();
+				if (withItem.getColumnLabels().size() > columns.length)
+					errors.addException(new ParseException("The WITH query \"" + withItem.getLabel() + "\" specifies MORE columns (" + withItem.getColumnLabels().size() + ") than available (" + columns.length + ")!", withItem.getPosition()));
+				else if (withItem.getColumnLabels().size() < columns.length)
+					errors.addException(new ParseException("The WITH query \"" + withItem.getLabel() + "\" specifies LESS columns (" + withItem.getColumnLabels().size() + ") than available (" + columns.length + ")!", withItem.getPosition()));
+			}
+		}
+
+		// Check the existence of all tables in the FROM clause:
 		sHandler = new SearchTableHandler();
 		sHandler.search(query.getFrom());
 		for(ADQLObject result : sHandler) {
@@ -593,19 +486,25 @@ public class DBChecker implements QueryChecker {
 				DBTable dbTable = null;
 				if (table.isSubQuery()) {
 					// check the sub-query tables:
-					check(table.getSubQuery(), fathersList);
+					check(table.getSubQuery(), contextList);
 					// generate its DBTable:
-					dbTable = generateDBTable(table.getSubQuery(), table.getAlias());
+					dbTable = generateDBTable(table.getSubQuery(), (table.isCaseSensitive(IdentifierField.ALIAS) ? "\"" + table.getAlias() + "\"" : table.getAlias()));
 				} else {
-					dbTable = resolveTable(table);
+					// search among DB tables:
+					if (dbTable == null)
+						dbTable = resolveTable(table, contextList);
 					// wrap this table metadata if an alias should be used:
-					if (dbTable != null && table.hasAlias())
-						dbTable = new DBTableAlias(dbTable, (table.isCaseSensitive(IdentifierField.ALIAS) ? table.getAlias() : table.getAlias().toLowerCase()));
+					if (dbTable != null && table.hasAlias()) {
+						dbTable = new DBTableAlias(dbTable, (table.isCaseSensitive(IdentifierField.ALIAS) ? "\"" + table.getAlias() + "\"" : table.getAlias().toLowerCase()));
+					}
 				}
 
 				// link with the matched DBTable:
 				table.setDBLink(dbTable);
-				mapTables.put(dbTable, table);
+				if (table.isSubQuery() || table.hasAlias()) {
+					if (!context.cteTables.add(dbTable))
+						errors.addException(new ParseException("Table name already used: \"" + dbTable.getADQLName() + "\". Please, choose a different alias for this table."));
+				}
 			} catch(ParseException pe) {
 				errors.addException(pe);
 			}
@@ -624,39 +523,39 @@ public class DBChecker implements QueryChecker {
 				ADQLTable table = wildcard.getAdqlTable();
 				DBTable dbTable = null;
 
-				// first, try to resolve the table by table alias:
-				if (table.getTableName() != null && table.getSchemaName() == null) {
-					List<ADQLTable> tables = query.getFrom().getTablesByAlias(table.getTableName(), table.isCaseSensitive(IdentifierField.TABLE));
-					if (tables.size() == 1)
-						dbTable = tables.get(0).getDBLink();
-				}
-
-				// then try to resolve the table reference by table name:
-				if (dbTable == null)
-					dbTable = resolveTable(table);
+				// resolve the table reference:
+				dbTable = resolveTable(table, contextList);
 
 				// set the corresponding tables among the list of resolved tables:
-				wildcard.setAdqlTable(mapTables.get(dbTable));
+				//wildcard.setAdqlTable(mapTables.get(dbTable));
+				wildcard.getAdqlTable().setDBLink(dbTable);
 			} catch(ParseException pe) {
 				errors.addException(pe);
 			}
 		}
-
-		return mapTables;
 	}
 
 	/**
-	 * Resolve the given table, that's to say search for the corresponding {@link DBTable}.
+	 * Resolve the given table, that's to say search for the corresponding
+	 * {@link DBTable}.
 	 *
-	 * @param table	The table to resolve.
+	 * @param table			The table to resolve.
+	 * @param contextList	Each item of this stack represents a recursion level
+	 *                   	inside the main ADQL query. A such item contains the
+	 *                   	list of columns and tables available at this level.
 	 *
-	 * @return		The corresponding {@link DBTable} if found, <i>null</i> otherwise.
+	 * @return	The corresponding {@link DBTable} if found.
 	 *
-	 * @throws ParseException	An {@link UnresolvedTableException} if the given table can't be resolved.
+	 * @throws ParseException	An {@link UnresolvedTableException} if the given
+	 *                       	table can't be resolved.
 	 */
-	protected DBTable resolveTable(final ADQLTable table) throws ParseException {
+	protected DBTable resolveTable(final ADQLTable table, final Stack<CheckContext> contextList) throws ParseException {
+		// search among fix tables:
 		List<DBTable> tables = lstTables.search(table);
 
+		// complete the search with CTEs:
+		tables.addAll(contextList.peek().cteTables.search(table));
+
 		// good if only one table has been found:
 		if (tables.size() == 1)
 			return tables.get(0);
@@ -673,7 +572,7 @@ public class DBChecker implements QueryChecker {
 	 * to the given tables' metadata, and if there is only one match, attach the
 	 * matching metadata to them.
 	 *
-	 * <h4>Management of selected columns' references</h4>
+	 * <h3>Management of selected columns' references</h3>
 	 * <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
@@ -689,20 +588,53 @@ public class DBChecker implements QueryChecker {
 	 *
 	 * @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 contextList	Each item of this stack represents a recursion level
+	 *                   	inside the main ADQL query. A such item contains the
+	 *                   	list of columns and tables available at this level.
 	 * @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.
+	 *
+	 * @deprecated	Since v2.0, the parameter 'mapTables' is no more used.
+	 *            	You should used {@link #resolveColumns(ADQLQuery, Stack, UnresolvedIdentifiersException)} instead.
+	 */
+	@Deprecated
+	protected final void resolveColumns(final ADQLQuery query, final Stack<CheckContext> contextList, final Map<DBTable, ADQLTable> mapTables, final UnresolvedIdentifiersException errors) {
+		resolveColumns(query, contextList, errors);
+	}
+
+	/**
+	 * 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.
+	 *
+	 * <h3>Management of selected columns' references</h3>
+	 * <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.
+	 * </p>
+	 * <p>
+	 * 	These references are also checked, in second steps, in this function.
+	 * 	Column metadata are also attached to them, as common columns.
+	 * </p>
+	 *
+	 * @param query			Query in which the existence of columns must be
+	 *             			checked.
+	 * @param contextList	Each item of this stack represents a recursion level
+	 *                   	inside the main ADQL query. A such item contains the
+	 *                   	list of columns and tables available at this level.
+	 * @param errors		List of errors to complete in this function each
+	 *              		time an unknown table or column is encountered.
+	 *
+	 * @since 2.0
 	 */
-	protected void resolveColumns(final ADQLQuery query, final Stack<SearchColumnList> fathersList, final Map<DBTable, ADQLTable> mapTables, final SearchColumnList list, final UnresolvedIdentifiersException errors) {
+	protected void resolveColumns(final ADQLQuery query, final Stack<CheckContext> contextList, final UnresolvedIdentifiersException errors) {
 		ISearchHandler sHandler;
 
 		// Check the existence of all columns:
@@ -710,7 +642,7 @@ public class DBChecker implements QueryChecker {
 		sHandler.search(query);
 		for(ADQLObject result : sHandler) {
 			try {
-				resolveColumn((ADQLColumn)result, list, mapTables, fathersList);
+				resolveColumn((ADQLColumn)result, contextList);
 			} catch(ParseException pe) {
 				errors.addException(pe);
 			}
@@ -718,10 +650,10 @@ public class DBChecker implements QueryChecker {
 
 		// Check the GROUP BY items:
 		ClauseSelect select = query.getSelect();
-		checkGroupBy(query.getGroupBy(), select, list, mapTables, fathersList, errors);
+		checkGroupBy(query.getGroupBy(), select, contextList, errors);
 
 		// Check the ORDER BY items:
-		checkOrderBy(query.getOrderBy(), select, list, mapTables, fathersList, errors);
+		checkOrderBy(query.getOrderBy(), select, contextList, errors);
 
 		/* Check the correctness of all column references (= references to
 		 * selected columns):
@@ -734,11 +666,9 @@ public class DBChecker implements QueryChecker {
 			try {
 				ColumnReference colRef = (ColumnReference)result;
 				// resolve the column reference:
-				DBColumn dbColumn = checkColumnReference(colRef, select, list);
+				DBColumn dbColumn = checkColumnReference(colRef, select, contextList);
 				// link with the matched DBColumn:
 				colRef.setDBLink(dbColumn);
-				if (dbColumn != null)
-					colRef.setAdqlTable(mapTables.get(dbColumn.getTable()));
 			} catch(ParseException pe) {
 				errors.addException(pe);
 			}
@@ -746,26 +676,13 @@ public class DBChecker implements QueryChecker {
 	}
 
 	/**
-	 * 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 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>
+	 * Resolve the given column, that's to say search for the corresponding
+	 * {@link DBColumn}.
 	 *
 	 * @param column		The column to resolve.
-	 * @param dbColumns		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 contextList	Each item of this stack represents a recursion level
+	 *                   	inside the main ADQL query. A such item contains the
+	 *                   	list of columns and tables available at this level.
 	 *
 	 * @return 	The corresponding {@link DBColumn} if found.
 	 *        	Otherwise an exception is thrown.
@@ -774,72 +691,32 @@ public class DBChecker implements QueryChecker {
 	 *                       	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);
+	protected DBColumn resolveColumn(final ADQLColumn column, final Stack<CheckContext> contextList) throws ParseException {
+		List<DBColumn> foundColumns = contextList.peek().availableColumns.search(column);
 
 		// good if only one column has been found:
-		if (foundColumns.size() == 1)
+		if (foundColumns.size() == 1) {
+			column.setDBLink(foundColumns.get(0));
 			return foundColumns.get(0);
+		}
 		// but if more than one: ambiguous table reference !
 		else if (foundColumns.size() > 1) {
 			if (column.getTableName() == null)
 				throw new UnresolvedColumnException(column, (foundColumns.get(0).getTable() == null) ? "<NULL>" : (foundColumns.get(0).getTable().getADQLName() + "." + foundColumns.get(0).getADQLName()), (foundColumns.get(1).getTable() == null) ? "<NULL>" : (foundColumns.get(1).getTable().getADQLName() + "." + foundColumns.get(1).getADQLName()));
 			else
 				throw new UnresolvedTableException(column, (foundColumns.get(0).getTable() == null) ? "<NULL>" : foundColumns.get(0).getTable().getADQLName(), (foundColumns.get(1).getTable() == null) ? "<NULL>" : foundColumns.get(1).getTable().getADQLName());
-		}// otherwise (no match): unknown column !
+		}// otherwise (i.e. no direct match)...
 		else {
-			if (fathersList == null || fathersList.isEmpty())
-				throw new UnresolvedColumnException(column);
-			else {
-				Stack<SearchColumnList> subStack = new Stack<SearchColumnList>();
-				subStack.addAll(fathersList.subList(0, fathersList.size() - 1));
-				return resolveColumn(column, fathersList.peek(), subStack);
+			// ...try searching among columns of the parent queries:
+			if (contextList.size() > 1) {
+				Stack<CheckContext> subStack = new Stack<CheckContext>();
+				subStack.addAll(contextList.subList(0, contextList.size() - 1));
+				return resolveColumn(column, subStack);
 			}
+			// ...else, unknown column!
+			else
+				throw new UnresolvedColumnException(column);
 		}
 	}
 
@@ -849,20 +726,15 @@ public class DBChecker implements QueryChecker {
 	 *
 	 * @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 contextList	Each item of this stack represents a recursion level
+	 *                   	inside the main ADQL query. A such item contains the
+	 *                   	list of columns and tables available at this level.
 	 * @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) {
+	protected void checkGroupBy(final ClauseADQL<ADQLOperand> groupBy, final ClauseSelect select, final Stack<CheckContext> contextList, final UnresolvedIdentifiersException errors) {
 		for(ADQLOperand obj : groupBy) {
 			try {
 				if (obj instanceof ADQLColumn) {
@@ -870,15 +742,15 @@ public class DBChecker implements QueryChecker {
 					/* resolve the column either as a selected column reference
 					 * or as a normal column: */
 					if (adqlColumn.getTableName() == null)
-						resolveColumnNameReference(adqlColumn, select, list, mapTables);
+						resolveColumnNameReference(adqlColumn, select, contextList);
 					else
-						resolveColumn(adqlColumn, list, fathersList);
+						resolveColumn(adqlColumn, contextList);
 				} else {
 					ISearchHandler sHandler = new SearchColumnHandler();
 					sHandler.search(obj);
 					for(ADQLObject result : sHandler) {
 						try {
-							resolveColumn((ADQLColumn)result, list, mapTables, fathersList);
+							resolveColumn((ADQLColumn)result, contextList);
 						} catch(ParseException pe) {
 							errors.addException(pe);
 						}
@@ -896,20 +768,15 @@ public class DBChecker implements QueryChecker {
 	 *
 	 * @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 contextList	Each item of this stack represents a recursion level
+	 *                   	inside the main ADQL query. A such item contains the
+	 *                   	list of columns and tables available at this level.
 	 * @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) {
+	protected void checkOrderBy(final ClauseADQL<ADQLOrder> orderBy, final ClauseSelect select, final Stack<CheckContext> contextList, final UnresolvedIdentifiersException errors) {
 		for(ADQLObject obj : orderBy) {
 			try {
 				ADQLOrder order = (ADQLOrder)obj;
@@ -920,15 +787,15 @@ public class DBChecker implements QueryChecker {
 						/* resolve the column either as a selected column reference
 						 * or as a normal column: */
 						if (adqlColumn.getTableName() == null)
-							resolveColumnNameReference(adqlColumn, select, list, mapTables);
+							resolveColumnNameReference(adqlColumn, select, contextList);
 						else
-							resolveColumn(adqlColumn, list, fathersList);
+							resolveColumn(adqlColumn, contextList);
 					} else {
 						ISearchHandler sHandler = new SearchColumnHandler();
 						sHandler.search(expr);
 						for(ADQLObject result : sHandler) {
 							try {
-								resolveColumn((ADQLColumn)result, list, mapTables, fathersList);
+								resolveColumn((ADQLColumn)result, contextList);
 							} catch(ParseException pe) {
 								errors.addException(pe);
 							}
@@ -947,41 +814,13 @@ public class DBChecker implements QueryChecker {
 	 *
 	 * @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, Stack)
-	 *
-	 * @since 1.4
-	 *
-	 * @deprecated	Since 2.0, this function has been renamed into
-	 *            	{@link #resolveColumnNameReference(ADQLColumn, ClauseSelect, SearchColumnList, Map)}.
-	 */
-	@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.
+	 * @param contextList	Each item of this stack represents a recursion level
+	 *                   	inside the main ADQL query. A such item contains the
+	 *                   	list of columns and tables available at this level.
 	 *
 	 * @return 	The corresponding {@link DBColumn} if this column corresponds to
 	 *        	an existing column,
-	 *        	<i>NULL</i> otherwise.
+	 *        	NULL otherwise.
 	 *
 	 * @throws ParseException	An {@link UnresolvedColumnException} if the
 	 *                       	given column can't be resolved
@@ -989,11 +828,11 @@ public class DBChecker implements QueryChecker {
 	 *                       	table reference can't be resolved.
 	 *
 	 * @see ClauseSelect#searchByAlias(String)
-	 * @see #resolveColumn(ADQLColumn, SearchColumnList, Map, Stack)
+	 * @see #resolveColumn(ADQLColumn, Stack)
 	 *
 	 * @since 2.0
 	 */
-	protected DBColumn resolveColumnNameReference(final ADQLColumn col, final ClauseSelect select, final SearchColumnList dbColumns, final Map<DBTable, ADQLTable> mapTables) throws ParseException {
+	protected DBColumn resolveColumnNameReference(final ADQLColumn col, final ClauseSelect select, final Stack<CheckContext> contextList) 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.*/
@@ -1004,7 +843,7 @@ 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, mapTables, null);
+		return resolveColumn(col, contextList);
 	}
 
 	/**
@@ -1013,7 +852,9 @@ public class DBChecker implements QueryChecker {
 	 *
 	 * @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.
+	 * @param contextList	Each item of this stack represents a recursion level
+	 *                   	inside the main ADQL query. A such item contains the
+	 *                   	list of columns and tables available at this level.
 	 *
 	 * @return 		The corresponding {@link DBColumn} if this reference is
 	 *        		actually referencing a selected column,
@@ -1024,7 +865,7 @@ public class DBChecker implements QueryChecker {
 	 * 							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 {
+	protected DBColumn checkColumnReference(final ColumnReference colRef, final ClauseSelect select, final Stack<CheckContext> contextList) throws ParseException {
 		int index = colRef.getColumnIndex();
 		if (index > 0 && index <= select.size()) {
 			SelectItem item = select.get(index - 1);
@@ -1037,46 +878,88 @@ public class DBChecker implements QueryChecker {
 	}
 
 	/**
-	 * Generate a {@link DBTable} corresponding to the given sub-query with the given table name.
-	 * This {@link DBTable} will contain all {@link DBColumn} returned by {@link ADQLQuery#getResultingColumns()}.
+	 * Generate a {@link DBTable} corresponding to the given sub-query with the
+	 * given table name. This {@link DBTable} will contain all {@link DBColumn}
+	 * returned by {@link ADQLQuery#getResultingColumns()}.
 	 *
 	 * @param subQuery	Sub-query in which the specified table must be searched.
 	 * @param tableName	Name of the table to search.
+	 *                 	<i>If between double quotes, the table name will be
+	 *                 	considered as case sensitive.</i>
 	 *
-	 * @return	The corresponding {@link DBTable} if the table has been found in the given sub-query, <i>null</i> otherwise.
+	 * @return	The corresponding {@link DBTable} if the table has been found in
+	 *        	the given sub-query,
+	 *        	or NULL otherwise.
 	 *
-	 * @throws ParseException	Can be used to explain why the table has not been found. <i>Note: not used by default.</i>
+	 * @throws ParseException	Can be used to explain why the table has not
+	 *                       	been found. <i>Not used by default.</i>
 	 */
 	public static DBTable generateDBTable(final ADQLQuery subQuery, final String tableName) throws ParseException {
-		DefaultDBTable dbTable = new DefaultDBTable(tableName);
+		// Create default DB meta:
+		DefaultDBTable dbTable = new DefaultDBTable((DefaultDBTable.isDelimited(tableName) ? tableName : tableName.toLowerCase()));
 
+		// Fetch all available columns:
 		DBColumn[] columns = subQuery.getResultingColumns();
+
+		// Add all available columns:
+		for(DBColumn dbCol : columns)
+			dbTable.addColumn(dbCol.copy(dbCol.getDBName(), DBIdentifier.denormalize(dbCol.getADQLName(), dbCol.isCaseSensitive()), dbTable));
+
+		return dbTable;
+	}
+
+	/**
+	 * Generate a {@link DBTable} corresponding to the given
+	 * Common Table Expression (i.e. CTE = item of a WITH clause).
+	 *
+	 * <p>
+	 * 	This {@link DBTable} will contain all {@link DBColumn}s returned by
+	 * 	{@link WithItem#getResultingColumns()}.
+	 * </p>
+	 *
+	 * @param withItem	CTE declaration.
+	 *
+	 * @return	The corresponding {@link DBTable},
+	 *        	or NULL otherwise.
+	 *
+	 * @since 2.0
+	 */
+	public static DBTable generateDBTable(final WithItem withItem) {
+		// Create default DB meta:
+		DefaultDBTable dbTable = new DefaultDBTable((withItem.isLabelCaseSensitive() ? withItem.getLabel() : withItem.getLabel().toLowerCase()));
+		dbTable.setCaseSensitive(withItem.isLabelCaseSensitive());
+
+		// Fetch all available columns:
+		DBColumn[] columns = withItem.getResultingColumns();
+
+		// Add all available columns:
 		for(DBColumn dbCol : columns)
-			dbTable.addColumn(dbCol.copy(dbCol.getADQLName(), dbCol.getADQLName(), dbTable));
+			dbTable.addColumn(dbCol.copy(dbCol.getDBName(), DBIdentifier.denormalize(dbCol.getADQLName(), dbCol.isCaseSensitive()), dbTable));
 
 		return dbTable;
 	}
 
-	/* ************************* */
-	/* CHECKING METHODS FOR UDFs */
-	/* ************************* */
+	/* **********************************************************************
+	   *                   CHECKING METHODS FOR UDFs                        *
+	   ********************************************************************** */
 
 	/**
-	 * <p>Search all UDFs (User Defined Functions) inside the given query, and then
-	 * check their signature against the list of allowed UDFs.</p>
+	 * Search all UDFs (User Defined Functions) inside the given query, and then
+	 * check their signature against the list of allowed UDFs.
 	 *
-	 * <p><i>Note:
-	 * 	When more than one allowed function match, the function is considered as correct
-	 * 	and no error is added.
-	 * 	However, in case of multiple matches, the return type of matching functions could
-	 * 	be different and in this case, there would be an error while checking later
-	 * 	the types. In such case, throwing an error could make sense, but the user would
-	 * 	then need to cast some parameters to help the parser identifying the right function.
+	 * <p><i><b>Note:</b>
+	 * 	When more than one allowed function match, the function is considered as
+	 * 	correct and no error is added. However, in case of multiple matches, the
+	 * 	return type of matching functions could be different and in this case,
+	 * 	there would be an error while checking later the types. In such case,
+	 * 	throwing an error could make sense, but the user would then need to cast
+	 * 	some parameters to help the parser identifying the right function.
 	 * 	But the type-casting ability is not yet possible in ADQL.
 	 * </i></p>
 	 *
 	 * @param query		Query in which UDFs must be checked.
-	 * @param errors	List of errors to complete in this function each time a UDF does not match to any of the allowed UDFs.
+	 * @param errors	List of errors to complete in this function each time a
+	 *              	UDF does not match to any of the allowed UDFs.
 	 *
 	 * @since 1.3
 	 */
@@ -1145,19 +1028,23 @@ public class DBChecker implements QueryChecker {
 	}
 
 	/**
-	 * <p>Tell whether the type of all parameters of the given ADQL function
-	 * is resolved.</p>
+	 * Tell whether the type of all parameters of the given ADQL function
+	 * is resolved.
 	 *
 	 * <p>A parameter type may not be resolved for 2 main reasons:</p>
 	 * <ul>
-	 * 	<li>the parameter is a <b>column</b>, but this column has not been successfully resolved. Thus its type is still unknown.</li>
-	 * 	<li>the parameter is a <b>UDF</b>, but this UDF has not been already resolved. Thus, as for the column, its return type is still unknown.
-	 * 		But it could be known later if the UDF is resolved later ; a second try should be done afterwards.</li>
+	 * 	<li>the parameter is a <b>column</b>, but this column has not been
+	 * 		successfully resolved. Thus its type is still unknown.</li>
+	 * 	<li>the parameter is a <b>UDF</b>, but this UDF has not been already
+	 * 		resolved. Thus, as for the column, its return type is still unknown.
+	 * 		But it could be known later if the UDF is resolved later ; a second
+	 * 		try should be done afterwards.</li>
 	 * </ul>
 	 *
 	 * @param fct	ADQL function whose the parameters' type should be checked.
 	 *
-	 * @return	<i>true</i> if the type of all parameters is known, <i>false</i> otherwise.
+	 * @return	<code>true</code> if the type of all parameters is known,
+	 *        	<code>false</code> otherwise.
 	 *
 	 * @since 1.3
 	 */
@@ -1169,583 +1056,843 @@ public class DBChecker implements QueryChecker {
 		return true;
 	}
 
-	/* ************************************************************************************************* */
-	/* METHODS CHECKING THE GEOMETRIES (geometrical functions, coordinate systems and STC-S expressions) */
-	/* ************************************************************************************************* */
+	/* **********************************************************************
+	   *       METHODS CHECKING TYPES UNKNOWN WHILE CHECKING SYNTAX         *
+	   ********************************************************************** */
 
 	/**
-	 * <p>Check all geometries.</p>
+	 * Search all operands whose the type is not yet known and try to resolve it
+	 * now and to check whether it matches the type expected by the syntactic
+	 * parser.
 	 *
-	 * <p>Operations done in this function:</p>
-	 * <ol>
-	 * 	<li>Check that all explicit (string constant) coordinate system definitions are supported</i></li>
-	 * 	<li>Check all STC-S expressions (only in {@link RegionFunction} for the moment) and
-	 * 	    Apply the 2 previous checks on them</li>
-	 * </ol>
+	 * <p>
+	 * 	Only two operands may have an unresolved type: columns and user defined
+	 * 	functions. Indeed, their type can be resolved only if the list of
+	 * 	available columns and UDFs is known, and if columns and UDFs used in the
+	 * 	query are resolved successfully.
+	 * </p>
 	 *
-	 * <p><i><b>IMPORTANT note:</b>
-	 * 	Since v2.0, the check of supported geometrical functions is performed
-	 * 	directly in ADQLParser through the notion of Optional Features.
-	 * 	The declaration of supported geometrical functions must now be done
-	 * 	with {@link adql.parser.ADQLParser#getSupportedFeatures() ADQLParser.getSupportedFeatures()}
-	 * 	(see also {@link adql.parser.feature.FeatureSet FeatureSet}).
+	 * <p>
+	 * 	When an operand type is still unknown, they will own the three kinds of
+	 * 	type and so this function won't raise an error: it is thus automatically
+	 * 	on the expected type. This behavior is perfectly correct because if the
+	 * 	type is not resolved that means the item/operand has not been resolved
+	 * 	in the previous steps and so that an error about this item has already
+	 * 	been raised.
+	 * </p>
+	 *
+	 * <p><i><b>Important note:</b>
+	 * 	This function does not check the types exactly, but just roughly by
+	 * 	considering only three categories: string, numeric and geometry.
 	 * </i></p>
 	 *
-	 * @param query		Query in which geometries must be checked.
-	 * @param errors	List of errors to complete in this function each time a geometry item is not supported.
+	 * @param query		Query in which unknown types must be resolved and
+	 *             		checked.
+	 * @param errors	List of errors to complete in this function each time a
+	 *              	types does not match to the expected one.
 	 *
-	 * @see #resolveCoordinateSystems(ADQLQuery, UnresolvedIdentifiersException)
-	 * @see #resolveSTCSExpressions(ADQLQuery, BinarySearch, UnresolvedIdentifiersException)
+	 * @see UnknownType
 	 *
 	 * @since 1.3
-	 *
-	 * @deprecated	Since 2.0, validation of the geometric functions is
-	 *            	performed automatically by
-	 *            	{@link adql.parser.ADQLParser ADQLParser}. Geometric
-	 *            	functions are optional features and should be declared as
-	 *            	such in the {@link adql.parser.ADQLParser ADQLParser} if
-	 *            	they are supported (see
-	 *            	{@link adql.parser.ADQLParser#getSupportedFeatures() ADQLParser.getSupportedFeatures()}).
 	 */
-	@Deprecated
-	protected void checkGeometries(final ADQLQuery query, final UnresolvedIdentifiersException errors) {
-		BinarySearch<String, String> binSearch = new BinarySearch<String, String>() {
-			@Override
-			protected int compare(String searchItem, String arrayItem) {
-				return searchItem.compareToIgnoreCase(arrayItem);
-			}
-		};
-
-		// a. Check whether the coordinate systems are allowed:
-		if (allowedCoordSys != null)
-			resolveCoordinateSystems(query, errors);
+	protected void checkTypes(final ADQLQuery query, final UnresolvedIdentifiersException errors) {
+		// Search all unknown types:
+		ISearchHandler sHandler = new SearchUnknownTypeHandler();
+		sHandler.search(query);
 
-		// b. Check all STC-S expressions (in RegionFunctions only) + the used coordinate systems (if StringConstant only):
-		if (allowedGeo == null || (allowedGeo.length > 0 && binSearch.search("REGION", allowedGeo) >= 0))
-			resolveSTCSExpressions(query, binSearch, errors);
+		// Check whether their type matches the expected one:
+		UnknownType unknown;
+		for(ADQLObject result : sHandler) {
+			unknown = (UnknownType)result;
+			switch(unknown.getExpectedType()) {
+				case 'G':
+				case 'g':
+					if (!unknown.isGeometry())
+						errors.addException(new ParseException("Type mismatch! A geometry was expected instead of \"" + unknown.toADQL() + "\".", result.getPosition()));
+					break;
+				case 'N':
+				case 'n':
+					if (!unknown.isNumeric())
+						errors.addException(new ParseException("Type mismatch! A numeric value was expected instead of \"" + unknown.toADQL() + "\".", result.getPosition()));
+					break;
+				case 'S':
+				case 's':
+					if (!unknown.isString())
+						errors.addException(new ParseException("Type mismatch! A string value was expected instead of \"" + unknown.toADQL() + "\".", result.getPosition()));
+					break;
+			}
+		}
 	}
 
+	/* **********************************************************************
+	   *               METHODS CHECKING THE SUB-QUERIES                     *
+	   ********************************************************************** */
+
 	/**
-	 * Search for all geometrical functions and check whether they are allowed.
+	 * Search for all sub-queries found in the given query but not in the clause
+	 * FROM. These sub-queries are then checked using
+	 * {@link #check(ADQLQuery, Stack)}.
 	 *
-	 * @param query		Query in which geometrical functions must be checked.
-	 * @param errors	List of errors to complete in this function each time a geometrical function is not supported.
 	 *
-	 * @see #checkGeometryFunction(String, ADQLFunction, BinarySearch, UnresolvedIdentifiersException)
+	 * @param query				Query in which sub-queries must be checked.
+	 * @param contextList		Each item of this stack represents a recursion
+	 *                   		level inside the main ADQL query. A such item
+	 *                   		contains the list of columns and tables
+	 *                   		available at this level.
+	 * @param availableColumns	List of all columns resolved in the given query.
+	 * @param errors			List of errors to complete in this function each
+	 *              			time a semantic error is encountered.
 	 *
 	 * @since 1.3
-	 *
-	 * @deprecated	Since 2.0, validation of the geometric functions is
-	 *            	performed automatically by
-	 *            	{@link adql.parser.ADQLParser ADQLParser}. Geometric
-	 *            	functions are optional features and should be declared as
-	 *            	such in the {@link adql.parser.ADQLParser ADQLParser} if
-	 *            	they are supported (see
-	 *            	{@link adql.parser.ADQLParser#getSupportedFeatures() ADQLParser.getSupportedFeatures()}).
 	 */
-	@Deprecated
-	protected final void resolveGeometryFunctions(final ADQLQuery query, final BinarySearch<String, String> binSearch, final UnresolvedIdentifiersException errors) {
-		ISearchHandler sHandler = new SearchGeometryHandler();
+	protected void checkSubQueries(final ADQLQuery query, final Stack<CheckContext> contextList, final UnresolvedIdentifiersException errors) {
+		// Check sub-queries outside the clause FROM:
+		ISearchHandler sHandler = new SearchSubQueryHandler();
 		sHandler.search(query);
+		if (sHandler.getNbMatch() > 0) {
 
-		String fctName;
-		for(ADQLObject result : sHandler) {
-			fctName = result.getName();
-			checkGeometryFunction(fctName, (ADQLFunction)result, binSearch, errors);
+			// Check each found sub-query:
+			for(ADQLObject result : sHandler) {
+				try {
+					check((ADQLQuery)result, contextList);
+				} catch(UnresolvedIdentifiersException uie) {
+					Iterator<ParseException> itPe = uie.getErrors();
+					while(itPe.hasNext())
+						errors.addException(itPe.next());
+				}
+			}
 		}
 	}
 
+	/* **********************************************************************
+	   *                        SEARCH HANDLERS                             *
+	   ********************************************************************** */
+
 	/**
-	 * <p>Check whether the specified geometrical function is allowed by this implementation.</p>
-	 *
-	 * <p><i>Note:
-	 * 	If the list of allowed geometrical functions is empty, this function will always add an errors to the given list.
-	 * 	Indeed, it means that no geometrical function is allowed and so that the specified function is automatically not supported.
-	 * </i></p>
+	 * Lets searching all {@link ADQLColumn} in the given object,
+	 * EXCEPT in the GROUP BY and ORDER BY clauses.
 	 *
-	 * @param fctName		Name of the geometrical function to test.
-	 * @param fct			The function instance being or containing the geometrical function to check. <i>Note: this function can be the function to test or a function embedding the function under test (i.e. RegionFunction).
-	 * @param binSearch		The object to use in order to search a function name inside the list of allowed functions.
-	 *                 		It is able to perform a binary search inside a sorted array of String objects. The interest of
-	 *                 		this object is its compare function which must be overridden and tells how to compare the item
-	 *                 		to search and the items of the array (basically, a non-case-sensitive comparison between 2 strings).
-	 * @param errors		List of errors to complete in this function each time a geometrical function is not supported.
+	 * <p>
+	 * 	{@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>
 	 *
-	 * @since 1.3
+	 * <p>
+	 * 	{@link ADQLColumn} of a {@link ColumnReference} may be an alias, they
+	 * 	can not be checked exactly as a normal column.
+	 * </p>
 	 *
-	 * @deprecated	Since 2.0, validation of the geometric functions is
-	 *            	performed automatically by
-	 *            	{@link adql.parser.ADQLParser ADQLParser}. Geometric
-	 *            	functions are optional features and should be declared as
-	 *            	such in the {@link adql.parser.ADQLParser ADQLParser} if
-	 *            	they are supported (see
-	 *            	{@link adql.parser.ADQLParser#getSupportedFeatures() ADQLParser.getSupportedFeatures()}).
+	 * @author Gr&eacute;gory Mantelet (ARI;CDS)
+	 * @version 2.0 (08/2019)
+	 * @since 1.4
 	 */
-	@Deprecated
-	protected final void checkGeometryFunction(final String fctName, final ADQLFunction fct, final BinarySearch<String, String> binSearch, final UnresolvedIdentifiersException errors) {
-		int match = -1;
-		if (allowedGeo.length != 0)
-			match = binSearch.search(fctName, allowedGeo);
-		if (match < 0)
-			errors.addException(new UnresolvedFunctionException("The geometrical function \"" + fctName + "\" is not available in this implementation!", fct));
+	private static class SearchColumnOutsideGroupByHandler extends SearchColumnHandler {
+		@Override
+		protected boolean goInto(final ADQLObject 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);
+		}
 	}
 
 	/**
-	 * <p>Search all explicit coordinate system declarations, check their syntax and whether they are allowed by this implementation.</p>
-	 *
-	 * <p><i>Note:
-	 * 	"explicit" means here that all {@link StringConstant} instances. Only coordinate systems expressed as string can
-	 * 	be parsed and so checked. So if a coordinate system is specified by a column, no check can be done at this stage...
-	 * 	it will be possible to perform such test only at the execution.
-	 * </i></p>
-	 *
-	 * @param query		Query in which coordinate systems must be checked.
-	 * @param errors	List of errors to complete in this function each time a coordinate system has a wrong syntax or is not supported.
-	 *
-	 * @see #checkCoordinateSystem(StringConstant, UnresolvedIdentifiersException)
-	 *
-	 * @since 1.3
+	 * Lets searching all tables.
 	 *
-	 * @deprecated	Since 2.0, the validation of coordinate systems is performed
-	 *            	automatically by {@link adql.parser.ADQLParser ADQLParser}.
+	 * @author Gr&eacute;gory Mantelet (CDS)
+	 * @version 1.0 (07/2011)
 	 */
-	@Deprecated
-	protected void resolveCoordinateSystems(final ADQLQuery query, final UnresolvedIdentifiersException errors) {
-		ISearchHandler sHandler = new SearchCoordSysHandler();
-		sHandler.search(query);
-		for(ADQLObject result : sHandler)
-			checkCoordinateSystem((StringConstant)result, errors);
+	private static class SearchTableHandler extends SimpleSearchHandler {
+		@Override
+		public boolean match(final ADQLObject obj) {
+			return obj instanceof ADQLTable;
+		}
 	}
 
 	/**
-	 * Parse and then check the coordinate system contained in the given {@link StringConstant} instance.
-	 *
-	 * @param adqlCoordSys	The {@link StringConstant} object containing the coordinate system to check.
-	 * @param errors		List of errors to complete in this function each time a coordinate system has a wrong syntax or is not supported.
-	 *
-	 * @see STCS#parseCoordSys(String)
-	 * @see #checkCoordinateSystem(adql.db.STCS.CoordSys, ADQLOperand, UnresolvedIdentifiersException)
+	 * Lets searching all wildcards.
 	 *
-	 * @since 1.3
-	 *
-	 * @deprecated	Since 2.0, the validation of coordinate systems is performed
-	 *            	automatically by {@link adql.parser.ADQLParser ADQLParser}.
+	 * @author Gr&eacute;gory Mantelet (CDS)
+	 * @version 1.0 (09/2011)
 	 */
-	@Deprecated
-	protected void checkCoordinateSystem(final StringConstant adqlCoordSys, final UnresolvedIdentifiersException errors) {
-		String coordSysStr = adqlCoordSys.getValue();
-		try {
-			checkCoordinateSystem(STCS.parseCoordSys(coordSysStr), adqlCoordSys, errors);
-		} catch(ParseException pe) {
-			errors.addException(new ParseException(pe.getMessage(), adqlCoordSys.getPosition()));
+	private static class SearchWildCardHandler extends SimpleSearchHandler {
+		@Override
+		public boolean match(final ADQLObject obj) {
+			return (obj instanceof SelectAllColumns) && (((SelectAllColumns)obj).getAdqlTable() != null);
 		}
 	}
 
 	/**
-	 * Check whether the given coordinate system is allowed by this implementation.
-	 *
-	 * @param coordSys	Coordinate system to test.
-	 * @param operand	The operand representing or containing the coordinate system under test.
-	 * @param errors	List of errors to complete in this function each time a coordinate system is not supported.
-	 *
-	 * @since 1.3
+	 * Lets searching column references.
 	 *
-	 * @deprecated	Since 2.0, the validation of coordinate systems is performed
-	 *            	automatically by {@link adql.parser.ADQLParser ADQLParser}.
+	 * @author Gr&eacute;gory Mantelet (CDS)
+	 * @version 1.0 (11/2011)
 	 */
-	@Deprecated
-	protected void checkCoordinateSystem(final CoordSys coordSys, final ADQLOperand operand, final UnresolvedIdentifiersException errors) {
-		if (coordSysRegExp != null && coordSys != null && !coordSys.toFullSTCS().matches(coordSysRegExp)) {
-			StringBuffer buf = new StringBuffer();
-			if (allowedCoordSys != null) {
-				for(String cs : allowedCoordSys) {
-					if (buf.length() > 0)
-						buf.append(", ");
-					buf.append(cs);
-				}
-			}
-			if (buf.length() == 0)
-				buf.append("No coordinate system is allowed!");
-			else
-				buf.insert(0, "Allowed coordinate systems are: ");
-			errors.addException(new ParseException("Coordinate system \"" + ((operand instanceof StringConstant) ? ((StringConstant)operand).getValue() : coordSys.toString()) + "\" (= \"" + coordSys.toFullSTCS() + "\") not allowed in this implementation. " + buf.toString(), operand.getPosition()));
+	private static class SearchColReferenceHandler extends SimpleSearchHandler {
+		@Override
+		public boolean match(final ADQLObject obj) {
+			return (obj instanceof ColumnReference);
 		}
 	}
 
 	/**
-	 * <p>Search all STC-S expressions inside the given query, parse them (and so check their syntax) and then determine
-	 * whether the declared coordinate system and the expressed region are allowed in this implementation.</p>
+	 * Lets searching subqueries in every clause except the WITH and FROM ones
+	 * (hence the modification of the {@link #goInto(ADQLObject)}.</p>
 	 *
-	 * <p><i>Note:
-	 * 	In the current ADQL language definition, STC-S expressions can be found only as only parameter of the REGION function.
+	 * <p><i><b>Note:</b>
+	 * 	The function {@link #addMatch(ADQLObject, ADQLIterator)} has been
+	 * 	modified in order to not have the root search object (here: the main
+	 * 	query) in the list of results.
 	 * </i></p>
 	 *
-	 * @param query			Query in which STC-S expressions must be checked.
-	 * @param binSearch		The object to use in order to search a region name inside the list of allowed functions/regions.
-	 *                 		It is able to perform a binary search inside a sorted array of String objects. The interest of
-	 *                 		this object is its compare function which must be overridden and tells how to compare the item
-	 *                 		to search and the items of the array (basically, a non-case-sensitive comparison between 2 strings).
-	 * @param errors		List of errors to complete in this function each time the STC-S syntax is wrong or each time the declared coordinate system or region is not supported.
-	 *
-	 * @see STCS#parseRegion(String)
-	 * @see #checkRegion(adql.db.STCS.Region, RegionFunction, BinarySearch, UnresolvedIdentifiersException)
-	 *
-	 * @since 1.3
-	 *
-	 * @deprecated	Since 2.0, the validation of STCs expressions is performed
-	 *            	automatically by {@link adql.parser.ADQLParser ADQLParser}.
+	 * @author Gr&eacute;gory Mantelet (ARI;CDS)
+	 * @version 2.0 (08/2019)
+	 * @since 1.2
 	 */
-	@Deprecated
-	protected void resolveSTCSExpressions(final ADQLQuery query, final BinarySearch<String, String> binSearch, final UnresolvedIdentifiersException errors) {
-		// Search REGION functions:
-		ISearchHandler sHandler = new SearchRegionHandler();
-		sHandler.search(query);
-
-		// Parse and check their STC-S expression:
-		String stcs;
-		Region region;
-		for(ADQLObject result : sHandler) {
-			try {
-				// get the STC-S expression:
-				stcs = ((StringConstant)((RegionFunction)result).getParameter(0)).getValue();
+	private static class SearchSubQueryHandler extends SimpleSearchHandler {
+		@Override
+		protected void addMatch(ADQLObject matchObj, ADQLIterator it) {
+			if (it != null)
+				super.addMatch(matchObj, it);
+		}
 
-				// parse the STC-S expression (and so check the syntax):
-				region = STCS.parseRegion(stcs);
+		@Override
+		protected boolean goInto(ADQLObject obj) {
+			return super.goInto(obj) && !(obj instanceof FromContent) && !(obj instanceof ClauseADQL && "WITH".equals(obj.getName()));
+		}
 
-				// check whether the regions (this one + the possible inner ones) and the coordinate systems are allowed:
-				checkRegion(region, (RegionFunction)result, binSearch, errors);
-			} catch(ParseException pe) {
-				errors.addException(new ParseException(pe.getMessage(), result.getPosition()));
-			}
+		@Override
+		protected boolean match(ADQLObject obj) {
+			return (obj instanceof ADQLQuery);
 		}
 	}
 
 	/**
-	 * <p>Check the given region.</p>
-	 *
-	 * <p>The following points are checked in this function:</p>
-	 * <ul>
-	 * 	<li>whether the coordinate system is allowed</li>
-	 * 	<li>whether the type of region is allowed</li>
-	 * 	<li>whether the inner regions are correct (here this function is called recursively on each inner region).</li>
-	 * </ul>
+	 * Let searching user defined functions.
 	 *
-	 * @param r			The region to check.
-	 * @param fct		The REGION function containing the region to check.
-	 * @param errors	List of errors to complete in this function if the given region or its inner regions are not supported.
+	 * @author Gr&eacute;gory Mantelet (ARI)
+	 * @version 1.3 (10/2014)
+	 * @since 1.3
+	 */
+	private static class SearchUDFHandler extends SimpleSearchHandler {
+		@Override
+		protected boolean match(ADQLObject obj) {
+			return (obj instanceof UserDefinedFunction);
+		}
+	}
+
+	/**
+	 * Let replacing every {@link DefaultUDF}s whose a {@link FunctionDef} is
+	 * set by their corresponding {@link UserDefinedFunction} class.
 	 *
-	 * @see #checkCoordinateSystem(adql.db.STCS.CoordSys, ADQLOperand, UnresolvedIdentifiersException)
-	 * @see #checkGeometryFunction(String, ADQLFunction, BinarySearch, UnresolvedIdentifiersException)
-	 * @see #checkRegion(adql.db.STCS.Region, RegionFunction, BinarySearch, UnresolvedIdentifiersException)
+	 * <p><i><b>Important note:</b>
+	 * 	If the replacer can not be created using the class returned by
+	 * 	{@link FunctionDef#getUDFClass()}, no replacement is performed.
+	 * </i></p>
 	 *
+	 * @author Gr&eacute;gory Mantelet (ARI)
+	 * @version 1.3 (02/2015)
 	 * @since 1.3
-	 *
-	 * @deprecated	Since 2.0, the validation of REGIONs is performed
-	 *            	automatically by {@link adql.parser.ADQLParser ADQLParser}.
 	 */
-	@Deprecated
-	protected void checkRegion(final Region r, final RegionFunction fct, final BinarySearch<String, String> binSearch, final UnresolvedIdentifiersException errors) {
-		if (r == null)
-			return;
+	private static class ReplaceDefaultUDFHandler extends SimpleReplaceHandler {
+		private final UnresolvedIdentifiersException errors;
 
-		// Check the coordinate system (if any):
-		if (r.coordSys != null)
-			checkCoordinateSystem(r.coordSys, fct, errors);
+		public ReplaceDefaultUDFHandler(final UnresolvedIdentifiersException errorsContainer) {
+			errors = errorsContainer;
+		}
 
-		// Check that the region type is allowed:
-		if (allowedGeo != null) {
-			if (allowedGeo.length == 0)
-				errors.addException(new UnresolvedFunctionException("The region type \"" + r.type + "\" is not available in this implementation!", fct));
-			else
-				checkGeometryFunction((r.type == RegionType.POSITION) ? "POINT" : r.type.toString(), fct, binSearch, errors);
+		@Override
+		protected boolean match(ADQLObject obj) {
+			return (obj.getClass().getName().equals(DefaultUDF.class.getName())) && (((DefaultUDF)obj).getDefinition() != null) && (((DefaultUDF)obj).getDefinition().getUDFClass() != null);
+			/* Note: detection of DefaultUDF is done on the exact class name rather than using "instanceof" in order to have only direct instances of DefaultUDF,
+			 * and not extensions of it. Indeed, DefaultUDFs are generally created automatically by the ADQLQueryFactory ; so, extensions of it can only be custom
+			 * UserDefinedFunctions. */
 		}
 
-		// Check all the inner regions:
-		if (r.regions != null) {
-			for(Region innerR : r.regions)
-				checkRegion(innerR, fct, binSearch, errors);
+		@Override
+		protected ADQLObject getReplacer(ADQLObject objToReplace) throws UnsupportedOperationException {
+			try {
+				// get the associated UDF class:
+				Class<? extends UserDefinedFunction> udfClass = ((DefaultUDF)objToReplace).getDefinition().getUDFClass();
+				// get the constructor with a single parameter of type ADQLOperand[]:
+				Constructor<? extends UserDefinedFunction> constructor = udfClass.getConstructor(ADQLOperand[].class);
+				// create a new instance of this UDF class with the operands stored in the object to replace:
+				return constructor.newInstance((Object)(((DefaultUDF)objToReplace).getParameters())); /* note: without this class, each item of the given array will be considered as a single parameter. */
+			} catch(Exception ex) {
+				// IF NO INSTANCE CAN BE CREATED...
+				// ...keep the error for further report:
+				errors.addException(new UnresolvedFunctionException("Impossible to represent the function \"" + ((DefaultUDF)objToReplace).getName() + "\": the following error occured while creating this representation: \"" + ((ex instanceof InvocationTargetException) ? "[" + ex.getCause().getClass().getSimpleName() + "] " + ex.getCause().getMessage() : ex.getMessage()) + "\"", (DefaultUDF)objToReplace));
+				// ...keep the same object (i.e. no replacement):
+				return objToReplace;
+			}
 		}
 	}
 
-	/* **************************************************** */
-	/* METHODS CHECKING TYPES UNKNOWN WHILE CHECKING SYNTAX */
-	/* **************************************************** */
-
 	/**
-	 * <p>Search all operands whose the type is not yet known and try to resolve it now
-	 * and to check whether it matches the type expected by the syntactic parser.</p>
-	 *
-	 * <p>
-	 * 	Only two operands may have an unresolved type: columns and user defined functions.
-	 * 	Indeed, their type can be resolved only if the list of available columns and UDFs is known,
-	 * 	and if columns and UDFs used in the query are resolved successfully.
-	 * </p>
-	 *
-	 * <p>
-	 * 	When an operand type is still unknown, they will own the three kinds of type and
-	 * 	so this function won't raise an error: it is thus automatically on the expected type.
-	 * 	This behavior is perfectly correct because if the type is not resolved
-	 * 	that means the item/operand has not been resolved in the previous steps and so that
-	 * 	an error about this item has already been raised.
-	 * </p>
+	 * Let searching all ADQL objects whose the type was not known while
+	 * checking the syntax of the ADQL query. These objects are
+	 * {@link ADQLColumn}s and {@link UserDefinedFunction}s.
 	 *
 	 * <p><i><b>Important note:</b>
-	 * 	This function does not check the types exactly, but just roughly by considering only three categories:
-	 * 	string, numeric and geometry.
+	 * 	Only {@link UnknownType} instances having an expected type equals to 'S'
+	 * 	(or 's' ; for string) or 'N' (or 'n' ; for numeric) are kept by this
+	 * 	handler. Others are ignored.
 	 * </i></p>
 	 *
-	 * @param query		Query in which unknown types must be resolved and checked.
-	 * @param errors	List of errors to complete in this function each time a types does not match to the expected one.
-	 *
-	 * @see UnknownType
-	 *
+	 * @author Gr&eacute;gory Mantelet (ARI)
+	 * @version 1.3 (10/2014)
 	 * @since 1.3
 	 */
-	protected void checkTypes(final ADQLQuery query, final UnresolvedIdentifiersException errors) {
-		// Search all unknown types:
-		ISearchHandler sHandler = new SearchUnknownTypeHandler();
-		sHandler.search(query);
-
-		// Check whether their type matches the expected one:
-		UnknownType unknown;
-		for(ADQLObject result : sHandler) {
-			unknown = (UnknownType)result;
-			switch(unknown.getExpectedType()) {
-				case 'G':
-				case 'g':
-					if (!unknown.isGeometry())
-						errors.addException(new ParseException("Type mismatch! A geometry was expected instead of \"" + unknown.toADQL() + "\".", result.getPosition()));
-					break;
-				case 'N':
-				case 'n':
-					if (!unknown.isNumeric())
-						errors.addException(new ParseException("Type mismatch! A numeric value was expected instead of \"" + unknown.toADQL() + "\".", result.getPosition()));
-					break;
-				case 'S':
-				case 's':
-					if (!unknown.isString())
-						errors.addException(new ParseException("Type mismatch! A string value was expected instead of \"" + unknown.toADQL() + "\".", result.getPosition()));
-					break;
-			}
+	private static class SearchUnknownTypeHandler extends SimpleSearchHandler {
+		@Override
+		protected boolean match(ADQLObject obj) {
+			if (obj instanceof UnknownType) {
+				char expected = ((UnknownType)obj).getExpectedType();
+				return (expected == 'G' || expected == 'g' || expected == 'S' || expected == 's' || expected == 'N' || expected == 'n');
+			} else
+				return false;
 		}
 	}
 
-	/* ******************************** */
-	/* METHODS CHECKING THE SUB-QUERIES */
-	/* ******************************** */
-
 	/**
-	 * <p>Search all sub-queries found in the given query but not in the clause FROM.
-	 * These sub-queries are then checked using {@link #check(ADQLQuery, Stack)}.</p>
+	 * Implement the binary search algorithm over a sorted array.
 	 *
-	 * <b>Fathers stack</b>
 	 * <p>
-	 * 	Each time a sub-query must be checked with {@link #check(ADQLQuery, Stack)},
-	 * 	the list of all columns available in each of its father queries must be provided.
-	 * 	This function is composing itself this stack by adding the given list of available
-	 * 	columns (= all columns resolved in the given query) at the end of the given stack.
-	 * 	If this stack is given empty, then a new stack is created.
+	 * 	The only difference with the standard implementation of Java is
+	 * 	that this object lets perform research with a different type
+	 * 	of object than the types of array items.
 	 * </p>
+	 *
 	 * <p>
-	 * 	This modification of the given stack is just the execution time of this function.
-	 * 	Before returning, this function removes the last item of the stack.
+	 * 	For that reason, the "compare" function must always be implemented.
 	 * </p>
 	 *
+	 * @author Gr&eacute;gory Mantelet (ARI)
+	 * @version 1.3 (10/2014)
 	 *
-	 * @param query				Query in which sub-queries 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 availableColumns	List of all columns resolved in the given query.
-	 * @param errors			List of errors to complete in this function each time a semantic error is encountered.
+	 * @param <T>	Type of items stored in the array.
+	 * @param <S>	Type of the item to search.
 	 *
 	 * @since 1.3
 	 */
-	protected void checkSubQueries(final ADQLQuery query, Stack<SearchColumnList> fathersList, final SearchColumnList availableColumns, final UnresolvedIdentifiersException errors) {
-		// Check sub-queries outside the clause FROM:
-		ISearchHandler sHandler = new SearchSubQueryHandler();
-		sHandler.search(query);
-		if (sHandler.getNbMatch() > 0) {
-
-			// Push the list of columns into the father columns stack:
-			if (fathersList == null)
-				fathersList = new Stack<SearchColumnList>();
-			fathersList.push(availableColumns);
-
-			// Check each found sub-query:
-			for(ADQLObject result : sHandler) {
-				try {
-					check((ADQLQuery)result, fathersList);
-				} catch(UnresolvedIdentifiersException uie) {
-					Iterator<ParseException> itPe = uie.getErrors();
-					while(itPe.hasNext())
-						errors.addException(itPe.next());
-				}
-			}
+	protected static abstract class BinarySearch<T, S> {
+		private int s, e, m, comp;
 
-			// Pop the list of columns from the father columns stack:
-			fathersList.pop();
+		/**
+		 * Search the given item in the given array.
+		 *
+		 * <p>
+		 * 	In case the given object matches to several items of the array,
+		 * 	this function will return the smallest index, pointing thus to the
+		 * 	first of all matches.
+		 * </p>
+		 *
+		 * @param searchItem	Object for which a corresponding array item must
+		 *                  	be searched.
+		 * @param array			Array in which the given object must be searched.
+		 *
+		 * @return	The array index of the first item of all matches.
+		 */
+		public int search(final S searchItem, final T[] array) {
+			s = 0;
+			e = array.length - 1;
+			while(s < e) {
+				// middle of the sorted array:
+				m = s + ((e - s) / 2);
+				// compare the fct with the middle item of the array:
+				comp = compare(searchItem, array[m]);
+				// if the fct is after, trigger the inspection of the right part of the array:
+				if (comp > 0)
+					s = m + 1;
+				// otherwise, the left part:
+				else
+					e = m;
+			}
+			if (s != e || compare(searchItem, array[s]) != 0)
+				return -1;
+			else
+				return s;
 		}
+
+		/**
+		 * Compare the search item and the array item.
+		 *
+		 * @param searchItem	Item whose a corresponding value must be found in the array.
+		 * @param arrayItem		An item of the array.
+		 *
+		 * @return	Negative value if searchItem is less than arrayItem, 0 if they are equals, or a positive value if searchItem is greater.
+		 */
+		protected abstract int compare(final S searchItem, final T arrayItem);
 	}
 
-	/* *************** */
-	/* SEARCH HANDLERS */
-	/* *************** */
+	/* **********************************************************************
+	   *                DEPRECATED STUFF ABOUT GEOMETRIES                   *
+	   ********************************************************************** */
 
-	/**
-	 * Lets searching all {@link ADQLColumn} in the given object,
-	 * EXCEPT in the GROUP BY and ORDER BY clauses.
+	/** List of all allowed geometrical functions (i.e. CONTAINS, REGION, POINT,
+	 * COORD2, ...).
 	 *
 	 * <p>
-	 * 	{@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.
+	 * 	If this list is NULL, all geometrical functions are allowed.
+	 * 	However, if not, all items of this list must be the only allowed
+	 * 	geometrical functions. So, if the list is empty, no such function is
+	 * 	allowed.
 	 * </p>
 	 *
+	 * @since 1.3
+	 * @deprecated Since v2.0, supported geometrical functions must be declared
+	 *             in ADQLParser. */
+	@Deprecated
+	protected String[] allowedGeo = null;
+
+	/** <p>List of all allowed coordinate systems.</p>
 	 * <p>
-	 * 	{@link ADQLColumn} of a {@link ColumnReference} may be an alias, they
-	 * 	can not be checked exactly as a normal column.
+	 * 	Each item of this list must be of the form: "{frame} {refpos} {flavor}".
+	 * 	Each of these 3 items can be either of value, a list of values expressed with the syntax "({value1}|{value2}|...)"
+	 * 	or a '*' to mean all possible values.
+	 * </p>
+	 * <p><i>Note: since a default value (corresponding to the empty string - '') should always be possible for each part of a coordinate system,
+	 * the checker will always add the default value (UNKNOWNFRAME, UNKNOWNREFPOS or SPHERICAL2) into the given list of possible values for each coord. sys. part.</i></p>
+	 * <p>
+	 * 	If this list is NULL, all coordinates systems are allowed.
+	 * 	However, if not, all items of this list must be the only allowed coordinate systems.
+	 * 	So, if the list is empty, none is allowed.
 	 * </p>
+	 * @since 1.3
+	 * @deprecated Since v2.0, supported coordinate systems must be declared
+	 *             in ADQLParser. */
+	@Deprecated
+	protected String[] allowedCoordSys = null;
+
+	/** <p>A regular expression built using the list of allowed coordinate systems.
+	 * With this regex, it is possible to known whether a coordinate system expression is allowed or not.</p>
+	 * <p>If NULL, all coordinate systems are allowed.</p>
+	 * @since 1.3
+	 * @deprecated Since v2.0, supported coordinate systems must be declared
+	 *             in ADQLParser. */
+	@Deprecated
+	protected String coordSysRegExp = null;
+
+	/**
+	 * <p>Builds a {@link DBChecker} with the given list of known tables and with a restricted list of user defined functions.</p>
 	 *
-	 * @author Gr&eacute;gory Mantelet (ARI;CDS)
-	 * @version 2.0 (08/2019)
-	 * @since 1.4
+	 * <p>Verifications done by this object after creation:</p>
+	 * <ul>
+	 * 	<li>Existence of tables and columns:            <b>OK</b></li>
+	 * 	<li>Existence of User Defined Functions (UDFs): <b>NO <i>(any "unknown" function is allowed)</i></b></li>
+	 * 	<li>Support of geometrical functions:           <b>OK</b></li>
+	 * 	<li>Support of coordinate systems:              <b>OK</b></li>
+	 * </ul>
+	 *
+	 * @param tables			List of all available tables.
+	 * @param allowedGeoFcts	List of all allowed geometrical functions (i.e. CONTAINS, POINT, UNION, CIRCLE, COORD1).
+	 *                      	If NULL, no verification will be done (and so, all geometries are allowed).
+	 *                      	If empty list, no geometry function is allowed.
+	 *                      	<i>Note: match with items of this list are done case insensitively.</i>
+	 * @param allowedCoordSys	List of all allowed coordinate system patterns. The syntax of a such pattern is the following:
+	 *                       	"{frame} {refpos} {flavor}" ; on the contrary to a coordinate system expression, here no part is optional.
+	 *                       	Each part of this pattern can be one the possible values (case insensitive), a list of possible values
+	 *                       	expressed with the syntax "({value1}|{value2}|...)", or a '*' for any valid value.
+	 *                       	For instance: "ICRS (GEOCENTER|heliocenter) *".
+	 *                       	If the given list is NULL, no verification will be done (and so, all coordinate systems are allowed).
+	 *                       	If it is empty, no coordinate system is allowed (except the default values - generally expressed by an empty string: '').
+	 *
+	 * @since 1.3
+	 * @deprecated	Since v2.0, the check of geometrical functions support is
+	 *            	performed in ADQLParser. It must now be done with
+	 *            	{@link adql.parser.ADQLParser#getSupportedFeatures() ADQLParser.getSupportedFeatures()}
+	 * 	          	(see also {@link adql.parser.feature.FeatureSet FeatureSet}).
 	 */
-	private static class SearchColumnOutsideGroupByHandler extends SearchColumnHandler {
-		@Override
-		protected boolean goInto(final ADQLObject 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);
+	@Deprecated
+	public DBChecker(final Collection<? extends DBTable> tables, final Collection<String> allowedGeoFcts, final Collection<String> allowedCoordSys) throws ParseException {
+		this(tables, null, allowedGeoFcts, allowedCoordSys);
+	}
+
+	/**
+	 * <p>Builds a {@link DBChecker}.</p>
+	 *
+	 * <p>Verifications done by this object after creation:</p>
+	 * <ul>
+	 * 	<li>Existence of tables and columns:            <b>OK</b></li>
+	 * 	<li>Existence of User Defined Functions (UDFs): <b>OK</b></li>
+	 * 	<li>Support of coordinate systems:              <b>OK</b></li>
+	 * </ul>
+	 *
+	 * <p><i><b>IMPORTANT note:</b>
+	 * 	Since v2.0, the check of supported geometrical functions is performed
+	 * 	directly in ADQLParser through the notion of Optional Features.
+	 * 	The declaration of supported geometrical functions must now be done
+	 * 	with {@link adql.parser.ADQLParser#getSupportedFeatures() ADQLParser.getSupportedFeatures()}
+	 * 	(see also {@link adql.parser.feature.FeatureSet FeatureSet}).
+	 * </i></p>
+	 *
+	 * @param tables			List of all available tables.
+	 * @param allowedUdfs		List of all allowed user defined functions.
+	 *                   		If NULL, no verification will be done (and so, all UDFs are allowed).
+	 *                   		If empty list, no "unknown" (or UDF) is allowed.
+	 *                   		<i>Note: match with items of this list are done case insensitively.</i>
+	 * @param allowedGeoFcts	List of all allowed geometrical functions (i.e. CONTAINS, POINT, UNION, CIRCLE, COORD1).
+	 *                      	If NULL, no verification will be done (and so, all geometries are allowed).
+	 *                      	If empty list, no geometry function is allowed.
+	 *                      	<i>Note: match with items of this list are done case insensitively.</i>
+	 * @param allowedCoordSys	List of all allowed coordinate system patterns. The syntax of a such pattern is the following:
+	 *                       	"{frame} {refpos} {flavor}" ; on the contrary to a coordinate system expression, here no part is optional.
+	 *                       	Each part of this pattern can be one the possible values (case insensitive), a list of possible values
+	 *                       	expressed with the syntax "({value1}|{value2}|...)", or a '*' for any valid value.
+	 *                       	For instance: "ICRS (GEOCENTER|heliocenter) *".
+	 *                       	If the given list is NULL, no verification will be done (and so, all coordinate systems are allowed).
+	 *                       	If it is empty, no coordinate system is allowed (except the default values - generally expressed by an empty string: '').
+	 *
+	 * @since 2.0
+	 * @deprecated	Since v2.0, the check of geometrical functions support is
+	 *            	performed in ADQLParser. It must now be done with
+	 *            	{@link adql.parser.ADQLParser#getSupportedFeatures() ADQLParser.getSupportedFeatures()}
+	 * 	          	(see also {@link adql.parser.feature.FeatureSet FeatureSet}).
+	 */
+	@Deprecated
+	public DBChecker(final Collection<? extends DBTable> tables, final Collection<? extends FunctionDef> allowedUdfs, final Collection<String> allowedGeoFcts, final Collection<String> allowedCoordSys) throws ParseException {
+		// Set the list of available tables + Set the list of all known UDFs:
+		this(tables, allowedUdfs);
+
+		// Set the list of allowed geometrical functions:
+		allowedGeo = specialSort(allowedGeoFcts);
+
+		// Set the list of allowed coordinate systems:
+		this.allowedCoordSys = specialSort(allowedCoordSys);
+		coordSysRegExp = STCS.buildCoordSysRegExp(this.allowedCoordSys);
+	}
+
+	/**
+	 * Transform the given collection of string elements in a sorted array.
+	 * Only non-NULL and non-empty strings are kept.
+	 *
+	 * @param items	Items to copy and sort.
+	 *
+	 * @return	A sorted array containing all - except NULL and empty strings - items of the given collection.
+	 *
+	 * @since 1.3
+	 *
+	 * @deprecated	Since v2.0, this tool function is no longer used. It was
+	 *            	useful only to collect allowed geometries and coordinate
+	 *            	systems....but these are now checked by
+	 *            	{@link adql.parser.ADQLParser ADQLParser}.
+	 */
+	@Deprecated
+	protected final static String[] specialSort(final Collection<String> items) {
+		// Nothing to do if the array is NULL:
+		if (items == null)
+			return null;
+
+		// Keep only valid items (not NULL and not empty string):
+		String[] tmp = new String[items.size()];
+		int cnt = 0;
+		for(String item : items) {
+			if (item != null && item.trim().length() > 0)
+				tmp[cnt++] = item;
 		}
+
+		// Make an adjusted array copy:
+		String[] copy = new String[cnt];
+		System.arraycopy(tmp, 0, copy, 0, cnt);
+
+		// Sort the values:
+		Arrays.sort(copy);
+
+		return copy;
 	}
 
 	/**
-	 * Lets searching all tables.
+	 * <p>Check all geometries.</p>
 	 *
-	 * @author Gr&eacute;gory Mantelet (CDS)
-	 * @version 1.0 (07/2011)
+	 * <p>Operations done in this function:</p>
+	 * <ol>
+	 * 	<li>Check that all explicit (string constant) coordinate system definitions are supported</i></li>
+	 * 	<li>Check all STC-S expressions (only in {@link RegionFunction} for the moment) and
+	 * 	    Apply the 2 previous checks on them</li>
+	 * </ol>
+	 *
+	 * <p><i><b>IMPORTANT note:</b>
+	 * 	Since v2.0, the check of supported geometrical functions is performed
+	 * 	directly in ADQLParser through the notion of Optional Features.
+	 * 	The declaration of supported geometrical functions must now be done
+	 * 	with {@link adql.parser.ADQLParser#getSupportedFeatures() ADQLParser.getSupportedFeatures()}
+	 * 	(see also {@link adql.parser.feature.FeatureSet FeatureSet}).
+	 * </i></p>
+	 *
+	 * @param query		Query in which geometries must be checked.
+	 * @param errors	List of errors to complete in this function each time a geometry item is not supported.
+	 *
+	 * @see #resolveCoordinateSystems(ADQLQuery, UnresolvedIdentifiersException)
+	 * @see #resolveSTCSExpressions(ADQLQuery, BinarySearch, UnresolvedIdentifiersException)
+	 *
+	 * @since 1.3
+	 *
+	 * @deprecated	Since 2.0, validation of the geometric functions is
+	 *            	performed automatically by
+	 *            	{@link adql.parser.ADQLParser ADQLParser}. Geometric
+	 *            	functions are optional features and should be declared as
+	 *            	such in the {@link adql.parser.ADQLParser ADQLParser} if
+	 *            	they are supported (see
+	 *            	{@link adql.parser.ADQLParser#getSupportedFeatures() ADQLParser.getSupportedFeatures()}).
 	 */
-	private static class SearchTableHandler extends SimpleSearchHandler {
-		@Override
-		public boolean match(final ADQLObject obj) {
-			return obj instanceof ADQLTable;
+	@Deprecated
+	protected void checkGeometries(final ADQLQuery query, final UnresolvedIdentifiersException errors) {
+		BinarySearch<String, String> binSearch = new BinarySearch<String, String>() {
+			@Override
+			protected int compare(String searchItem, String arrayItem) {
+				return searchItem.compareToIgnoreCase(arrayItem);
+			}
+		};
+
+		// a. Check whether the coordinate systems are allowed:
+		if (allowedCoordSys != null)
+			resolveCoordinateSystems(query, errors);
+
+		// b. Check all STC-S expressions (in RegionFunctions only) + the used coordinate systems (if StringConstant only):
+		if (allowedGeo == null || (allowedGeo.length > 0 && binSearch.search("REGION", allowedGeo) >= 0))
+			resolveSTCSExpressions(query, binSearch, errors);
+	}
+
+	/**
+	 * Search for all geometrical functions and check whether they are allowed.
+	 *
+	 * @param query		Query in which geometrical functions must be checked.
+	 * @param errors	List of errors to complete in this function each time a geometrical function is not supported.
+	 *
+	 * @see #checkGeometryFunction(String, ADQLFunction, BinarySearch, UnresolvedIdentifiersException)
+	 *
+	 * @since 1.3
+	 *
+	 * @deprecated	Since 2.0, validation of the geometric functions is
+	 *            	performed automatically by
+	 *            	{@link adql.parser.ADQLParser ADQLParser}. Geometric
+	 *            	functions are optional features and should be declared as
+	 *            	such in the {@link adql.parser.ADQLParser ADQLParser} if
+	 *            	they are supported (see
+	 *            	{@link adql.parser.ADQLParser#getSupportedFeatures() ADQLParser.getSupportedFeatures()}).
+	 */
+	@Deprecated
+	protected final void resolveGeometryFunctions(final ADQLQuery query, final BinarySearch<String, String> binSearch, final UnresolvedIdentifiersException errors) {
+		ISearchHandler sHandler = new SearchGeometryHandler();
+		sHandler.search(query);
+
+		String fctName;
+		for(ADQLObject result : sHandler) {
+			fctName = result.getName();
+			checkGeometryFunction(fctName, (ADQLFunction)result, binSearch, errors);
 		}
 	}
 
 	/**
-	 * Lets searching all wildcards.
+	 * <p>Check whether the specified geometrical function is allowed by this implementation.</p>
 	 *
-	 * @author Gr&eacute;gory Mantelet (CDS)
-	 * @version 1.0 (09/2011)
+	 * <p><i>Note:
+	 * 	If the list of allowed geometrical functions is empty, this function will always add an errors to the given list.
+	 * 	Indeed, it means that no geometrical function is allowed and so that the specified function is automatically not supported.
+	 * </i></p>
+	 *
+	 * @param fctName		Name of the geometrical function to test.
+	 * @param fct			The function instance being or containing the geometrical function to check. <i>Note: this function can be the function to test or a function embedding the function under test (i.e. RegionFunction).
+	 * @param binSearch		The object to use in order to search a function name inside the list of allowed functions.
+	 *                 		It is able to perform a binary search inside a sorted array of String objects. The interest of
+	 *                 		this object is its compare function which must be overridden and tells how to compare the item
+	 *                 		to search and the items of the array (basically, a non-case-sensitive comparison between 2 strings).
+	 * @param errors		List of errors to complete in this function each time a geometrical function is not supported.
+	 *
+	 * @since 1.3
+	 *
+	 * @deprecated	Since 2.0, validation of the geometric functions is
+	 *            	performed automatically by
+	 *            	{@link adql.parser.ADQLParser ADQLParser}. Geometric
+	 *            	functions are optional features and should be declared as
+	 *            	such in the {@link adql.parser.ADQLParser ADQLParser} if
+	 *            	they are supported (see
+	 *            	{@link adql.parser.ADQLParser#getSupportedFeatures() ADQLParser.getSupportedFeatures()}).
 	 */
-	private static class SearchWildCardHandler extends SimpleSearchHandler {
-		@Override
-		public boolean match(final ADQLObject obj) {
-			return (obj instanceof SelectAllColumns) && (((SelectAllColumns)obj).getAdqlTable() != null);
+	@Deprecated
+	protected final void checkGeometryFunction(final String fctName, final ADQLFunction fct, final BinarySearch<String, String> binSearch, final UnresolvedIdentifiersException errors) {
+		int match = -1;
+		if (allowedGeo.length != 0)
+			match = binSearch.search(fctName, allowedGeo);
+		if (match < 0)
+			errors.addException(new UnresolvedFunctionException("The geometrical function \"" + fctName + "\" is not available in this implementation!", fct));
+	}
+
+	/**
+	 * <p>Search all explicit coordinate system declarations, check their syntax and whether they are allowed by this implementation.</p>
+	 *
+	 * <p><i>Note:
+	 * 	"explicit" means here that all {@link StringConstant} instances. Only coordinate systems expressed as string can
+	 * 	be parsed and so checked. So if a coordinate system is specified by a column, no check can be done at this stage...
+	 * 	it will be possible to perform such test only at the execution.
+	 * </i></p>
+	 *
+	 * @param query		Query in which coordinate systems must be checked.
+	 * @param errors	List of errors to complete in this function each time a coordinate system has a wrong syntax or is not supported.
+	 *
+	 * @see #checkCoordinateSystem(StringConstant, UnresolvedIdentifiersException)
+	 *
+	 * @since 1.3
+	 *
+	 * @deprecated	Since 2.0, the validation of coordinate systems is performed
+	 *            	automatically by {@link adql.parser.ADQLParser ADQLParser}.
+	 */
+	@Deprecated
+	protected void resolveCoordinateSystems(final ADQLQuery query, final UnresolvedIdentifiersException errors) {
+		ISearchHandler sHandler = new SearchCoordSysHandler();
+		sHandler.search(query);
+		for(ADQLObject result : sHandler)
+			checkCoordinateSystem((StringConstant)result, errors);
+	}
+
+	/**
+	 * Parse and then check the coordinate system contained in the given {@link StringConstant} instance.
+	 *
+	 * @param adqlCoordSys	The {@link StringConstant} object containing the coordinate system to check.
+	 * @param errors		List of errors to complete in this function each time a coordinate system has a wrong syntax or is not supported.
+	 *
+	 * @see STCS#parseCoordSys(String)
+	 * @see #checkCoordinateSystem(adql.db.STCS.CoordSys, ADQLOperand, UnresolvedIdentifiersException)
+	 *
+	 * @since 1.3
+	 *
+	 * @deprecated	Since 2.0, the validation of coordinate systems is performed
+	 *            	automatically by {@link adql.parser.ADQLParser ADQLParser}.
+	 */
+	@Deprecated
+	protected void checkCoordinateSystem(final StringConstant adqlCoordSys, final UnresolvedIdentifiersException errors) {
+		String coordSysStr = adqlCoordSys.getValue();
+		try {
+			checkCoordinateSystem(STCS.parseCoordSys(coordSysStr), adqlCoordSys, errors);
+		} catch(ParseException pe) {
+			errors.addException(new ParseException(pe.getMessage(), adqlCoordSys.getPosition()));
 		}
 	}
 
 	/**
-	 * Lets searching column references.
+	 * Check whether the given coordinate system is allowed by this implementation.
 	 *
-	 * @author Gr&eacute;gory Mantelet (CDS)
-	 * @version 1.0 (11/2011)
+	 * @param coordSys	Coordinate system to test.
+	 * @param operand	The operand representing or containing the coordinate system under test.
+	 * @param errors	List of errors to complete in this function each time a coordinate system is not supported.
+	 *
+	 * @since 1.3
+	 *
+	 * @deprecated	Since 2.0, the validation of coordinate systems is performed
+	 *            	automatically by {@link adql.parser.ADQLParser ADQLParser}.
 	 */
-	private static class SearchColReferenceHandler extends SimpleSearchHandler {
-		@Override
-		public boolean match(final ADQLObject obj) {
-			return (obj instanceof ColumnReference);
+	@Deprecated
+	protected void checkCoordinateSystem(final CoordSys coordSys, final ADQLOperand operand, final UnresolvedIdentifiersException errors) {
+		if (coordSysRegExp != null && coordSys != null && !coordSys.toFullSTCS().matches(coordSysRegExp)) {
+			StringBuffer buf = new StringBuffer();
+			if (allowedCoordSys != null) {
+				for(String cs : allowedCoordSys) {
+					if (buf.length() > 0)
+						buf.append(", ");
+					buf.append(cs);
+				}
+			}
+			if (buf.length() == 0)
+				buf.append("No coordinate system is allowed!");
+			else
+				buf.insert(0, "Allowed coordinate systems are: ");
+			errors.addException(new ParseException("Coordinate system \"" + ((operand instanceof StringConstant) ? ((StringConstant)operand).getValue() : coordSys.toString()) + "\" (= \"" + coordSys.toFullSTCS() + "\") not allowed in this implementation. " + buf.toString(), operand.getPosition()));
 		}
 	}
 
 	/**
-	 * <p>Lets searching subqueries in every clause except the FROM one (hence the modification of the {@link #goInto(ADQLObject)}.</p>
+	 * <p>Search all STC-S expressions inside the given query, parse them (and so check their syntax) and then determine
+	 * whether the declared coordinate system and the expressed region are allowed in this implementation.</p>
 	 *
-	 * <p><i>
-	 * 	<u>Note:</u> The function {@link #addMatch(ADQLObject, ADQLIterator)} has been modified in order to
-	 * 	not have the root search object (here: the main query) in the list of results.
+	 * <p><i>Note:
+	 * 	In the current ADQL language definition, STC-S expressions can be found only as only parameter of the REGION function.
 	 * </i></p>
 	 *
-	 * @author Gr&eacute;gory Mantelet (ARI)
-	 * @version 1.2 (12/2013)
-	 * @since 1.2
+	 * @param query			Query in which STC-S expressions must be checked.
+	 * @param binSearch		The object to use in order to search a region name inside the list of allowed functions/regions.
+	 *                 		It is able to perform a binary search inside a sorted array of String objects. The interest of
+	 *                 		this object is its compare function which must be overridden and tells how to compare the item
+	 *                 		to search and the items of the array (basically, a non-case-sensitive comparison between 2 strings).
+	 * @param errors		List of errors to complete in this function each time the STC-S syntax is wrong or each time the declared coordinate system or region is not supported.
+	 *
+	 * @see STCS#parseRegion(String)
+	 * @see #checkRegion(adql.db.STCS.Region, RegionFunction, BinarySearch, UnresolvedIdentifiersException)
+	 *
+	 * @since 1.3
+	 *
+	 * @deprecated	Since 2.0, the validation of STCs expressions is performed
+	 *            	automatically by {@link adql.parser.ADQLParser ADQLParser}.
 	 */
-	private static class SearchSubQueryHandler extends SimpleSearchHandler {
-		@Override
-		protected void addMatch(ADQLObject matchObj, ADQLIterator it) {
-			if (it != null)
-				super.addMatch(matchObj, it);
-		}
+	@Deprecated
+	protected void resolveSTCSExpressions(final ADQLQuery query, final BinarySearch<String, String> binSearch, final UnresolvedIdentifiersException errors) {
+		// Search REGION functions:
+		ISearchHandler sHandler = new SearchRegionHandler();
+		sHandler.search(query);
 
-		@Override
-		protected boolean goInto(ADQLObject obj) {
-			return super.goInto(obj) && !(obj instanceof FromContent);
-		}
+		// Parse and check their STC-S expression:
+		String stcs;
+		Region region;
+		for(ADQLObject result : sHandler) {
+			try {
+				// get the STC-S expression:
+				stcs = ((StringConstant)((RegionFunction)result).getParameter(0)).getValue();
 
-		@Override
-		protected boolean match(ADQLObject obj) {
-			return (obj instanceof ADQLQuery);
-		}
-	}
+				// parse the STC-S expression (and so check the syntax):
+				region = STCS.parseRegion(stcs);
 
-	/**
-	 * Let searching user defined functions.
-	 *
-	 * @author Gr&eacute;gory Mantelet (ARI)
-	 * @version 1.3 (10/2014)
-	 * @since 1.3
-	 */
-	private static class SearchUDFHandler extends SimpleSearchHandler {
-		@Override
-		protected boolean match(ADQLObject obj) {
-			return (obj instanceof UserDefinedFunction);
+				// check whether the regions (this one + the possible inner ones) and the coordinate systems are allowed:
+				checkRegion(region, (RegionFunction)result, binSearch, errors);
+			} catch(ParseException pe) {
+				errors.addException(new ParseException(pe.getMessage(), result.getPosition()));
+			}
 		}
 	}
 
 	/**
-	 * <p>Let replacing every {@link DefaultUDF}s whose a {@link FunctionDef} is set by their corresponding {@link UserDefinedFunction} class.</p>
+	 * <p>Check the given region.</p>
 	 *
-	 * <p><i><b>Important note:</b>
-	 * 	If the replacer can not be created using the class returned by {@link FunctionDef#getUDFClass()}, no replacement is performed.
-	 * </i></p>
+	 * <p>The following points are checked in this function:</p>
+	 * <ul>
+	 * 	<li>whether the coordinate system is allowed</li>
+	 * 	<li>whether the type of region is allowed</li>
+	 * 	<li>whether the inner regions are correct (here this function is called recursively on each inner region).</li>
+	 * </ul>
+	 *
+	 * @param r			The region to check.
+	 * @param fct		The REGION function containing the region to check.
+	 * @param errors	List of errors to complete in this function if the given region or its inner regions are not supported.
+	 *
+	 * @see #checkCoordinateSystem(adql.db.STCS.CoordSys, ADQLOperand, UnresolvedIdentifiersException)
+	 * @see #checkGeometryFunction(String, ADQLFunction, BinarySearch, UnresolvedIdentifiersException)
+	 * @see #checkRegion(adql.db.STCS.Region, RegionFunction, BinarySearch, UnresolvedIdentifiersException)
 	 *
-	 * @author Gr&eacute;gory Mantelet (ARI)
-	 * @version 1.3 (02/2015)
 	 * @since 1.3
+	 *
+	 * @deprecated	Since 2.0, the validation of REGIONs is performed
+	 *            	automatically by {@link adql.parser.ADQLParser ADQLParser}.
 	 */
-	private static class ReplaceDefaultUDFHandler extends SimpleReplaceHandler {
-		private final UnresolvedIdentifiersException errors;
+	@Deprecated
+	protected void checkRegion(final Region r, final RegionFunction fct, final BinarySearch<String, String> binSearch, final UnresolvedIdentifiersException errors) {
+		if (r == null)
+			return;
 
-		public ReplaceDefaultUDFHandler(final UnresolvedIdentifiersException errorsContainer) {
-			errors = errorsContainer;
-		}
+		// Check the coordinate system (if any):
+		if (r.coordSys != null)
+			checkCoordinateSystem(r.coordSys, fct, errors);
 
-		@Override
-		protected boolean match(ADQLObject obj) {
-			return (obj.getClass().getName().equals(DefaultUDF.class.getName())) && (((DefaultUDF)obj).getDefinition() != null) && (((DefaultUDF)obj).getDefinition().getUDFClass() != null);
-			/* Note: detection of DefaultUDF is done on the exact class name rather than using "instanceof" in order to have only direct instances of DefaultUDF,
-			 * and not extensions of it. Indeed, DefaultUDFs are generally created automatically by the ADQLQueryFactory ; so, extensions of it can only be custom
-			 * UserDefinedFunctions. */
+		// Check that the region type is allowed:
+		if (allowedGeo != null) {
+			if (allowedGeo.length == 0)
+				errors.addException(new UnresolvedFunctionException("The region type \"" + r.type + "\" is not available in this implementation!", fct));
+			else
+				checkGeometryFunction((r.type == RegionType.POSITION) ? "POINT" : r.type.toString(), fct, binSearch, errors);
 		}
 
-		@Override
-		protected ADQLObject getReplacer(ADQLObject objToReplace) throws UnsupportedOperationException {
-			try {
-				// get the associated UDF class:
-				Class<? extends UserDefinedFunction> udfClass = ((DefaultUDF)objToReplace).getDefinition().getUDFClass();
-				// get the constructor with a single parameter of type ADQLOperand[]:
-				Constructor<? extends UserDefinedFunction> constructor = udfClass.getConstructor(ADQLOperand[].class);
-				// create a new instance of this UDF class with the operands stored in the object to replace:
-				return constructor.newInstance((Object)(((DefaultUDF)objToReplace).getParameters())); /* note: without this class, each item of the given array will be considered as a single parameter. */
-			} catch(Exception ex) {
-				// IF NO INSTANCE CAN BE CREATED...
-				// ...keep the error for further report:
-				errors.addException(new UnresolvedFunctionException("Impossible to represent the function \"" + ((DefaultUDF)objToReplace).getName() + "\": the following error occured while creating this representation: \"" + ((ex instanceof InvocationTargetException) ? "[" + ex.getCause().getClass().getSimpleName() + "] " + ex.getCause().getMessage() : ex.getMessage()) + "\"", (DefaultUDF)objToReplace));
-				// ...keep the same object (i.e. no replacement):
-				return objToReplace;
-			}
+		// Check all the inner regions:
+		if (r.regions != null) {
+			for(Region innerR : r.regions)
+				checkRegion(innerR, fct, binSearch, errors);
 		}
 	}
 
@@ -1755,7 +1902,10 @@ public class DBChecker implements QueryChecker {
 	 * @author Gr&eacute;gory Mantelet (ARI)
 	 * @version 1.3 (10/2014)
 	 * @since 1.3
+	 *
+	 * @deprecated	Since 2.0.
 	 */
+	@Deprecated
 	private static class SearchGeometryHandler extends SimpleSearchHandler {
 		@Override
 		protected boolean match(ADQLObject obj) {
@@ -1763,30 +1913,6 @@ public class DBChecker implements QueryChecker {
 		}
 	}
 
-	/**
-	 * <p>Let searching all ADQL objects whose the type was not known while checking the syntax of the ADQL query.
-	 * These objects are {@link ADQLColumn}s and {@link UserDefinedFunction}s.</p>
-	 *
-	 * <p><i><b>Important note:</b>
-	 * 	Only {@link UnknownType} instances having an expected type equals to 'S' (or 's' ; for string) or 'N' (or 'n' ; for numeric)
-	 * 	are kept by this handler. Others are ignored.
-	 * </i></p>
-	 *
-	 * @author Gr&eacute;gory Mantelet (ARI)
-	 * @version 1.3 (10/2014)
-	 * @since 1.3
-	 */
-	private static class SearchUnknownTypeHandler extends SimpleSearchHandler {
-		@Override
-		protected boolean match(ADQLObject obj) {
-			if (obj instanceof UnknownType) {
-				char expected = ((UnknownType)obj).getExpectedType();
-				return (expected == 'G' || expected == 'g' || expected == 'S' || expected == 's' || expected == 'N' || expected == 'n');
-			} else
-				return false;
-		}
-	}
-
 	/**
 	 * Let searching all explicit declaration of coordinate systems.
 	 * So, only {@link StringConstant} objects will be returned.
@@ -1834,75 +1960,4 @@ public class DBChecker implements QueryChecker {
 		}
 
 	}
-
-	/**
-	 * <p>Implement the binary search algorithm over a sorted array.</p>
-	 *
-	 * <p>
-	 * 	The only difference with the standard implementation of Java is
-	 * 	that this object lets perform research with a different type
-	 * 	of object than the types of array items.
-	 * </p>
-	 *
-	 * <p>
-	 * 	For that reason, the "compare" function must always be implemented.
-	 * </p>
-	 *
-	 * @author Gr&eacute;gory Mantelet (ARI)
-	 * @version 1.3 (10/2014)
-	 *
-	 * @param <T>	Type of items stored in the array.
-	 * @param <S>	Type of the item to search.
-	 *
-	 * @since 1.3
-	 */
-	protected static abstract class BinarySearch<T, S> {
-		private int s, e, m, comp;
-
-		/**
-		 * <p>Search the given item in the given array.</p>
-		 *
-		 * <p>
-		 * 	In case the given object matches to several items of the array,
-		 * 	this function will return the smallest index, pointing thus to the first
-		 * 	of all matches.
-		 * </p>
-		 *
-		 * @param searchItem	Object for which a corresponding array item must be searched.
-		 * @param array			Array in which the given object must be searched.
-		 *
-		 * @return	The array index of the first item of all matches.
-		 */
-		public int search(final S searchItem, final T[] array) {
-			s = 0;
-			e = array.length - 1;
-			while(s < e) {
-				// middle of the sorted array:
-				m = s + ((e - s) / 2);
-				// compare the fct with the middle item of the array:
-				comp = compare(searchItem, array[m]);
-				// if the fct is after, trigger the inspection of the right part of the array:
-				if (comp > 0)
-					s = m + 1;
-				// otherwise, the left part:
-				else
-					e = m;
-			}
-			if (s != e || compare(searchItem, array[s]) != 0)
-				return -1;
-			else
-				return s;
-		}
-
-		/**
-		 * Compare the search item and the array item.
-		 *
-		 * @param searchItem	Item whose a corresponding value must be found in the array.
-		 * @param arrayItem		An item of the array.
-		 *
-		 * @return	Negative value if searchItem is less than arrayItem, 0 if they are equals, or a positive value if searchItem is greater.
-		 */
-		protected abstract int compare(final S searchItem, final T arrayItem);
-	}
-
 }
diff --git a/src/adql/db/DBColumn.java b/src/adql/db/DBColumn.java
index 07988bfd0830e5c65002c5d233f10f30fceca177..ec4bc3687ae1c83e55bb772660603a66566b5b24 100644
--- a/src/adql/db/DBColumn.java
+++ b/src/adql/db/DBColumn.java
@@ -35,7 +35,20 @@ package adql.db;
 public interface DBColumn {
 
 	/**
-	 * Gets the name of this column (without any prefix and double-quotes).
+	 * Gets the name of this column.
+	 *
+	 * <i>
+	 * <p><b>Notes:</b>
+	 * 	The returned ADQL name is:
+	 * </p>
+	 * <ul>
+	 * 	<li>non-empty/NULL</li>
+	 * 	<li>non-delimited (i.e. not between double quotes),</li>
+	 * 	<li>non-prefixed (i.e. no table/schema/catalog name)</li>
+	 * 	<li>and in the same case as provided at initialization (even if not case
+	 * 		sensitive).</li>
+	 * </ul>
+	 * </i>
 	 *
 	 * @return	Its ADQL name.
 	 */
@@ -54,17 +67,31 @@ public interface DBColumn {
 	public boolean isCaseSensitive();
 
 	/**
-	 * Gets the name of this column in the "database".
+	 * Gets the name of this column in the "database" (e.g. as it should be used
+	 * in SQL queries).
+	 *
+	 * <i>
+	 * <p><b>Notes</b>
+	 * 	The returned DB name is:
+	 * </p>
+	 * <ul>
+	 * 	<li>non-empty/NULL</li>
+	 * 	<li>non-delimited (i.e. not between double quotes),</li>
+	 * 	<li>non-prefixed (i.e. no table/schema/catalog name)</li>
+	 * 	<li>and in the EXACT case as it MUST be used.</li>
+	 * </ul>
 	 *
 	 * @return	Its DB name.
 	 */
 	public String getDBName();
 
 	/**
-	 * <p>Get the type of this column (as closed as possible from the "database" type).</p>
+	 * Get the type of this column (as closed as possible from the "database"
+	 * type).
 	 *
-	 * <p><i>Note:
-	 * 	The returned type should be as closed as possible from a type listed by the IVOA in the TAP protocol description into the section UPLOAD.
+	 * <p><i><b>Note:</b>
+	 * 	The returned type should be as closed as possible from a type listed by
+	 * 	the IVOA in the TAP protocol description into the section UPLOAD.
 	 * </i></p>
 	 *
 	 * @return	Its type.
@@ -76,7 +103,8 @@ public interface DBColumn {
 	/**
 	 * Gets the table which contains this {@link DBColumn}.
 	 *
-	 * @return	Its table or <i>null</i> if no table is specified.
+	 * @return	Its table
+	 *        	or NULL if no table is specified.
 	 */
 	public DBTable getTable();
 
diff --git a/src/adql/db/DBIdentifier.java b/src/adql/db/DBIdentifier.java
new file mode 100644
index 0000000000000000000000000000000000000000..a85f8c01e8a2ccfc86a7005fd9474cbb1033c877
--- /dev/null
+++ b/src/adql/db/DBIdentifier.java
@@ -0,0 +1,344 @@
+package adql.db;
+
+/*
+ * This file is part of ADQLLibrary.
+ *
+ * ADQLLibrary is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * ADQLLibrary is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * 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 2019- UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ */
+
+/**
+ * Generic implementation of any kind of ADQL/DB identifier.
+ *
+ * <p>
+ * 	It already implements functions getting and setting the ADQL and DB names
+ * 	of the interfaces {@link DBTable} and {@link DBColumn}. Thus, it guarantees
+ * 	that all DB... identifiers will behave the same way when manipulating their
+ * 	ADQL and DB names.
+ * </p>
+ *
+ * @author Gr&eacute;gory Mantelet (CDS)
+ * @version 2.0 (09/2019)
+ * @since 2.0
+ *
+ * @see DBTable
+ * @see DBColumn
+ */
+public abstract class DBIdentifier {
+
+	/** Regular expression of a delimited identifier (i.e. an identifier between
+	 * double quotes ; an inner double quote is escaped by doubling it). */
+	private final static String REGEXP_DELIMITED = "\"(\"\"|[^\"])*\"";
+
+	/** Name (not delimited, not prefixed) to use in ADQL queries.
+	 * <p><i><b>Important:</b> It must never be NULL.</i></p> */
+	protected String adqlName = null;
+
+	/** A flag indicating if the ADQL name is case sensitive or not (i.e. if it
+	 * must be delimited or not in an ADQL query).  */
+	protected boolean adqlCaseSensitive = false;
+
+	/** Name (not delimited, not prefixed) of this identifier in the "database".
+	 * This name must be used, for example, while translating an ADQL query into
+	 * SQL.
+	 * <p><i><b>Note:</b> It may be NULL. In such case, {@link #getDBName()}
+	 * must return {@link #adqlName}.</i></p> */
+	protected String dbName = null;
+
+	/**
+	 * Create an identifier with the given ADQL name.
+	 *
+	 * <p>
+	 * 	In this constructor, the DB name is not set. Thus, {@link #getDBName()}
+	 * 	will return the same as {@link #getADQLName()}.
+	 * </p>
+	 *
+	 * <p><i><b>Note:</b>
+	 * 	If the given name is delimited, the surrounding double quotes will be
+	 * 	removed and {@link #isCaseSensitive()} will return <code>true</code>.
+	 * </i></p>
+	 *
+	 * @param adqlName	The ADQL and DB name of this identifier.
+	 *                	<i>It may be delimited and/or qualified.</i>
+	 *
+	 * @throws NullPointerException	If the given name is NULL or empty.
+	 *
+	 * @see #setADQLName(String)
+	 */
+	protected DBIdentifier(final String adqlName) throws NullPointerException {
+		setADQLName(adqlName);
+	}
+
+	/**
+	 * Create an identifier with the given ADQL and DB names.
+	 *
+	 * <p>
+	 * 	In this constructor, the DB name is not set. Thus, {@link #getDBName()}
+	 * 	will return the same as {@link #getADQLName()}.
+	 * </p>
+	 *
+	 * <p><i><b>Note:</b>
+	 * 	If the given name is delimited, the surrounding double quotes will be
+	 * 	removed and {@link #isCaseSensitive()} will return <code>true</code>.
+	 * </i></p>
+	 *
+	 * @param adqlName	The ADQL and DB name of this identifier.
+	 *                	<i>It may be delimited and/or qualified.</i>
+	 *
+	 * @throws NullPointerException	If the given name is NULL or empty.
+	 *
+	 * @see #setADQLName(String)
+	 * @see #setDBName(String)
+	 */
+	protected DBIdentifier(final String adqlName, final String dbName) throws NullPointerException {
+		setADQLName(adqlName);
+		setDBName(dbName);
+	}
+
+	/**
+	 * Get the ADQL version of this identifier.
+	 *
+	 * <p>
+	 * 	This name is neither delimited, nor prefixed.
+	 * 	To determine whether it should be delimited in an ADQL query, use
+	 * 	{@link #isCaseSensitive()}.
+	 * </p>
+	 *
+	 * <p><i><b>Note:</b>
+	 * 	The returned string is never empty or NULL.
+	 * </i></p>
+	 *
+	 * @return	The name to use in ADQL queries.
+	 */
+	public String getADQLName() {
+		return adqlName;
+	}
+
+	/**
+	 * Set the ADQL version of this identifier.
+	 *
+	 * <p>
+	 * 	If the given name is delimited, the surrounding double quotes will be
+	 * 	removed and case sensitivity will be set to <code>true</code>
+	 * 	(i.e. {@link #isCaseSensitive()} will return <code>true</code>).
+	 * </p>
+	 *
+	 * <p><i><b>Note:</b>
+	 * 	The given name must not be prefixed.
+	 * </i></p>
+	 *
+	 * <p><i><b>WARNING:</b>
+	 * 	If the given name is NULL or empty (even after removal of surrounding
+	 * 	double quotes, if delimited), this function will immediately throw an
+	 * 	exception.
+	 * </i></p>
+	 *
+	 * @param newName	New ADQL version of this identifier.
+	 *
+	 * @throws NullPointerException	If the given name is NULL or empty.
+	 *
+	 * @see #isDelimited(String)
+	 * @see #normalize(String)
+	 */
+	public void setADQLName(final String newName) throws NullPointerException {
+		boolean adqlCS = isDelimited(newName);
+		String normName = normalize(newName);
+
+		if (normName == null)
+			throw new NullPointerException("Missing ADQL name!");
+
+		this.adqlName = normName;
+		this.adqlCaseSensitive = adqlCS;
+	}
+
+	/**
+	 * Tell whether the ADQL version of this identifier is case sensitive or
+	 * not.
+	 *
+	 * <p>
+	 * 	If case sensitive, the ADQL name must be written between double quotes
+	 * 	(and all inner double quotes should be doubled).
+	 * </p>
+	 *
+	 * @return	<code>true</code> if case sensitive,
+	 *        	<code>false</code> otherwise.
+	 */
+	public boolean isCaseSensitive() {
+		return adqlCaseSensitive;
+	}
+
+	/**
+	 * Set the case sensitivity of the ADQL version of this identifier.
+	 *
+	 * <p>
+	 * 	Setting the case sensitivity to <code>true</code> will force the
+	 * 	delimited form of the ADQL name (i.e. it will be written between
+	 * 	double quotes).
+	 * </p>
+	 *
+	 * @param caseSensitive	<code>true</code> to declare the ADQL name as case
+	 *                     	sensitive,
+	 *                     	<code>false</code> otherwise.
+	 */
+	public void setCaseSensitive(final boolean caseSensitive) {
+		this.adqlCaseSensitive = caseSensitive;
+	}
+
+	/**
+	 * Get the database version of this identifier.
+	 *
+	 * <p>This name is neither delimited, nor prefixed.</p>
+	 *
+	 * <p>In an SQL query, this name should be considered as case sensitive.</p>
+	 *
+	 * <p><i><b>Note:</b>
+	 * 	The returned string is never empty or NULL.
+	 * </i></p>
+	 *
+	 * @return	The real name of this identifier in the "database".
+	 */
+	public String getDBName() {
+		return (dbName == null) ? getADQLName() : dbName;
+	}
+
+	/**
+	 * Set the database version of this identifier.
+	 *
+	 * <p>
+	 * 	If the given name is delimited, the surrounding double quotes will be
+	 * 	removed.
+	 * </p>
+	 *
+	 * <p><i><b>Note 1:</b>
+	 * 	The given name should not be prefixed.
+	 * </i></p>
+	 *
+	 * <p><i><b>Note 2:</b>
+	 * 	If the given name is NULL or empty (even after removal of surrounding
+	 * 	double quotes if delimited), {@link #getDBName()} will return the same
+	 * 	as {@link #getADQLName()}.
+	 * </i></p>
+	 *
+	 * @param newName	The real name of this identifier in the "database".
+	 *
+	 * @see #normalize(String)
+	 */
+	public void setDBName(final String newName) {
+		dbName = normalize(newName);
+	}
+
+	/**
+	 * Tell whether the given identifier is delimited (i.e. within the same pair
+	 * of double quotes - <code>"</code>).
+	 *
+	 * <i>
+	 * <p>The following identifiers ARE delimited:</p>
+	 * <ul>
+	 * 	<li><code>"a"</code></li>
+	 * 	<li><code>""</code> (empty string ; but won't be considered as a
+	 * 	                     valid ADQL name)</li>
+	 * 	<li><code>" "</code> (string with spaces ; but won't be considered as a
+	 * 	                      valid ADQL name)</li>
+	 * 	<li><code>"foo.bar"</code></li>
+	 * 	<li><code>"foo"".""bar"</code> (with escaped double quotes)</li>
+	 * 	<li><code>""""</code> (idem)</li>
+	 * </ul>
+	 * </i>
+	 *
+	 * <i>
+	 * <p>The following identifiers are NOT considered as delimited:</p>
+	 * <ul>
+	 * 	<li><code>"foo</code> (missing ending double quote)</li>
+	 * 	<li><code>foo"</code> (missing leading double quote)</li>
+	 * 	<li><code>"foo"."bar"</code> (not the same pair of double quotes)</li>
+	 * </ul>
+	 * </i>
+	 *
+	 * @param ident	Identifier that may be delimited.
+	 *
+	 * @return	<code>true</code> if the given identifier is delimited,
+	 *        	<code>false</code> otherwise.
+	 */
+	public static boolean isDelimited(final String ident) {
+		return ident != null && ident.trim().matches(REGEXP_DELIMITED);
+	}
+
+	/**
+	 * Normalize the given identifier.
+	 *
+	 * <p>This function performs the following operations:</p>
+	 * <ol>
+	 * 	<li>Remove leading and trailing space characters.</li>
+	 * 	<li>If the resulting string is empty, return NULL.</li>
+	 * 	<li>If {@link #isDelimited(String) delimited}, remove the leading and
+	 * 		trailing double quotes.</li>
+	 * 	<li>If the resulting string without leading and trailing spaces is
+	 * 		empty, return NULL.</li>
+	 * 	<li>Return the resulting string.</li>
+	 * </ol>
+	 *
+	 * @param ident	The identifier to normalize.
+	 *
+	 * @return	The normalized string,
+	 *        	or NULL if NULL or empty.
+	 *
+	 * @see #denormalize(String, boolean)
+	 */
+	public static String normalize(final String ident) {
+		// Return NULL if empty:
+		if (ident == null || ident.trim().length() == 0)
+			return null;
+
+		// Remove leading and trailing space characters:
+		String normIdent = ident.trim();
+
+		// If delimited, remove the leading and trailing ":
+		if (isDelimited(normIdent)) {
+			normIdent = normIdent.substring(1, normIdent.length() - 1).replaceAll("\"\"", "\"");
+			return (normIdent.trim().length() == 0) ? null : normIdent;
+		} else
+			return normIdent;
+	}
+
+	/**
+	 * De-normalize the given string.
+	 *
+	 * <p>
+	 * 	This function does something only if the given string is declared as
+	 * 	case sensitive. In such case, it will surround it by double quotes.
+	 * 	All inner double quotes will be escaped by doubling them.
+	 * </p>
+	 *
+	 * <p><i><b>Note:</b>
+	 * 	If the given string is NULL, it will be returned as such (i.e. NULL).
+	 * </i></p>
+	 *
+	 * @param ident			The identifier to de-normalize.
+	 * @param caseSensitive	<code>true</code> if the given identifier is
+	 *                     	considered as case sensitive,
+	 *                     	<code>false</code> otherwise.
+	 *
+	 * @return	The de-normalized identifier.
+	 *
+	 * @see #normalize(String)
+	 */
+	public static String denormalize(final String ident, final boolean caseSensitive) {
+		if (caseSensitive && ident != null)
+			return "\"" + ident.replaceAll("\"", "\"\"") + "\"";
+		else
+			return ident;
+	}
+
+}
diff --git a/src/adql/db/DBTable.java b/src/adql/db/DBTable.java
index 8ff8dea37d38b85578fefeca7c40bcea2827cc50..cefb4a3c8a4467b02b2776a0c0e8175d4d03c359 100644
--- a/src/adql/db/DBTable.java
+++ b/src/adql/db/DBTable.java
@@ -35,7 +35,20 @@ package adql.db;
 public interface DBTable extends Iterable<DBColumn> {
 
 	/**
-	 * Gets the name of this table (without any prefix and double-quotes).
+	 * Gets the name of this table in ADQL queries.
+	 *
+	 * <i>
+	 * <p><b>Notes:</b>
+	 * 	The returned ADQL name is:
+	 * </p>
+	 * <ul>
+	 * 	<li>non-empty/NULL</li>
+	 * 	<li>non-delimited (i.e. not between double quotes),</li>
+	 * 	<li>non-prefixed (i.e. no schema/catalog name)</li>
+	 * 	<li>and in the same case as provided at initialization (even if not case
+	 * 		sensitive).</li>
+	 * </ul>
+	 * </i>
 	 *
 	 * @return	Its ADQL name.
 	 */
@@ -54,7 +67,19 @@ public interface DBTable extends Iterable<DBColumn> {
 	public boolean isCaseSensitive();
 
 	/**
-	 * Gets the name of this table in the "database".
+	 * Gets the name of this table in the "database" (e.g. as it should be used
+	 * in SQL queries).
+	 *
+	 * <i>
+	 * <p><b>Notes</b>
+	 * 	The returned DB name is:
+	 * </p>
+	 * <ul>
+	 * 	<li>non-empty/NULL</li>
+	 * 	<li>non-delimited (i.e. not between double quotes),</li>
+	 * 	<li>non-prefixed (i.e. no schema/catalog name)</li>
+	 * 	<li>and in the EXACT case as it MUST be used.</li>
+	 * </ul>
 	 *
 	 * @return	Its DB name.
 	 */
@@ -63,6 +88,10 @@ public interface DBTable extends Iterable<DBColumn> {
 	/**
 	 * Gets the ADQL name of the schema which contains this table.
 	 *
+	 * <p><i><b>Warning!</b>
+	 * 	Same rules as {@link #getADQLName()}.
+	 * </i></p>
+	 *
 	 * @return	ADQL name of its schema.
 	 */
 	public String getADQLSchemaName();
@@ -70,6 +99,10 @@ public interface DBTable extends Iterable<DBColumn> {
 	/**
 	 * Gets the DB name of the schema which contains this table.
 	 *
+	 * <p><i><b>Warning!</b>
+	 * 	Same rules as {@link #getDBName()}.
+	 * </i></p>
+	 *
 	 * @return	DB name of its schema.
 	 */
 	public String getDBSchemaName();
@@ -77,6 +110,10 @@ public interface DBTable extends Iterable<DBColumn> {
 	/**
 	 * Gets the ADQL name of the catalog which contains this table.
 	 *
+	 * <p><i><b>Warning!</b>
+	 * 	Same rules as {@link #getADQLName()}.
+	 * </i></p>
+	 *
 	 * @return	ADQL name of its catalog.
 	 */
 	public String getADQLCatalogName();
@@ -84,6 +121,10 @@ public interface DBTable extends Iterable<DBColumn> {
 	/**
 	 * Gets the DB name of the catalog which contains this table.
 	 *
+	 * <p><i><b>Warning!</b>
+	 * 	Same rules as {@link #getDBName()}.
+	 * </i></p>
+	 *
 	 * @return	DB name of its catalog.
 	 */
 	public String getDBCatalogName();
@@ -91,36 +132,54 @@ public interface DBTable extends Iterable<DBColumn> {
 	/**
 	 * Gets the definition of the specified column if it exists in this table.
 	 *
-	 * @param colName		Name of the column <i>(may be the ADQL or DB name depending of the second parameter)</i>.
-	 * @param adqlName		<i>true</i> means the given name is the ADQL name of the column and that the research must be done on the ADQL name of columns,
-	 * 						<i>false</i> means the same thing but with the DB name.
+	 * @param colName	Name of the column <i>(may be the ADQL or DB name
+	 *               	depending of the second parameter)</i>.
+	 * @param adqlName	<code>true</code> means the given name is the ADQL name
+	 *                	of the column and that the research must be done on the
+	 *                	ADQL name of columns,
+	 * 					<code>false</code> means the same thing but with the DB
+	 *                	name.
 	 *
-	 * @return				The corresponding column, or <i>null</i> if the specified column had not been found.
+	 * @return	The corresponding column,
+	 *        	or NULL if the specified column had not been found.
 	 */
 	public DBColumn getColumn(String colName, boolean adqlName);
 
 	/**
-	 * <p>Makes a copy of this instance of {@link DBTable}, with the possibility to change the DB and ADQL names.</p>
+	 * Makes a copy of this instance of {@link DBTable}, with the possibility
+	 * to change the DB and ADQL names.
 	 *
-	 * <p><b>IMPORTANT:</b>
-	 * 	<b>The given DB and ADQL name may be NULL.</b> If NULL, the copy will contain exactly the same full name (DB and/or ADQL).<br/>
-	 * 	<b>And they may be qualified</b> (that's to say: prefixed by the schema name or by the catalog and schema name). It means that it is possible to
-	 * 	change the catalog, schema and table name in the copy.<br/>
-	 * 	For instance:
-	 * </p>
+	 * <p><b>IMPORTANT:</b></p>
+	 * <ul>
+	 * 	<li><b>The given DB and ADQL name may be NULL.</b> If NULL, the copy
+	 * 		will contain exactly the same full name (DB and/or ADQL).</li>
+	 * 	<li><b>they may be qualified</b> (that's to say: prefixed by the
+	 * 		schema name or by the catalog and schema name). It means that it is
+	 * 		possible to change the catalog, schema and table name in the copy.</li>
+	 * 	<li><b>they may be delimited</b> (that's to say: written between double
+	 * 		quotes to force case sensitivity).</li>
+	 * </ul>
+	 * <i>
+	 * <p><b>For instance:</b></p>
 	 * <ul>
-	 * 	<li><i>.copy(null, "foo") =></i> a copy with the same full DB name, but with no ADQL catalog and schema name and with an ADQL table name equals to "foo"</li>
-	 * 	<li><i>.copy("schema.table", ) =></i> a copy with the same full ADQL name, but with no DB catalog name, with a DB schema name equals to "schema" and with a DB table name equals to "table"</li>
+	 * 	<li><code>.copy(null, "foo")</code> => a copy with the same full DB
+	 * 		name, but with no ADQL catalog and schema name and with an ADQL
+	 * 		table name equals to "foo"</li>
+	 * 	<li><code>.copy("schema.table", null)</code> => a copy with the same
+	 * 		full ADQL name, but with no DB catalog name, with a DB schema name
+	 * 		equals to "schema" and with a DB table name equals to "table"</li>
 	 * </ul>
+	 * </i>
 	 *
 	 * @param dbName	Its new DB name.
-	 *              	It may be qualified.
-	 *              	It may also be NULL ; if so, the full DB name won't be different in the copy.
-	 * @param adqlName	Its new ADQL name.
-	 *              	It may be qualified.
-	 *              	It may also be NULL ; if so, the full DB name won't be different in the copy.
+	 *              	It may be qualified and/or delimited.
+	 *              	It may also be NULL ; if so, the full DB name won't be
+	 *              	different in the copy.
+	 * @param adqlName	Its new ADQL name. It may be qualified and/or delimited.
+	 *              	It may also be NULL ; if so, the full DB name won't be
+	 *              	different in the copy.
 	 *
-	 * @return			A modified copy of this {@link DBTable}.
+	 * @return	A modified copy of this {@link DBTable}.
 	 */
 	public DBTable copy(final String dbName, final String adqlName);
 }
diff --git a/src/adql/db/DBTableAlias.java b/src/adql/db/DBTableAlias.java
index aef451e0a74a3ffc999cd656b1f24d913f34cf75..5e2cfd3446b1f16dc65e99180450465b4e6211a8 100644
--- a/src/adql/db/DBTableAlias.java
+++ b/src/adql/db/DBTableAlias.java
@@ -16,9 +16,14 @@ package adql.db;
  * 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 2017 - Astronomisches Rechen Institut (ARI)
+ * Copyright 2017-2019 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomisches Rechen Institut (ARI)
  */
 
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
 /**
  * This {@link DBTable} wraps another {@link DBTable} with a different ADQL and
  * DB name.
@@ -41,11 +46,13 @@ package adql.db;
  * 	{@link #getOriginTable()}.
  * </i></p>
  *
- * @author Gr&eacute;gory Mantelet (ARI)
- * @version 1.4 (11/2017)
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 2.0 (09/2019)
  * @since 1.4
  */
-public class DBTableAlias extends DefaultDBTable {
+public final class DBTableAlias extends DBIdentifier implements DBTable {
+
+	protected final Map<String, DBColumn> columns = new LinkedHashMap<String, DBColumn>();
 
 	/** Wrapped table. */
 	protected final DBTable originTable;
@@ -56,13 +63,13 @@ public class DBTableAlias extends DefaultDBTable {
 	 * @param originTable	The table to wrap/alias.
 	 * @param tableAlias	The alias name.
 	 */
-	public DBTableAlias(final DBTable originTable, final String tableAlias){
-		super(null, null, tableAlias);
+	public DBTableAlias(final DBTable originTable, final String tableAlias) {
+		super(tableAlias);
 
 		this.originTable = originTable;
 
 		for(DBColumn col : originTable)
-			addColumn(col.copy(col.getDBName(), col.getADQLName(), this));
+			columns.put(col.getADQLName(), col.copy(col.getDBName(), denormalize(col.getADQLName(), col.isCaseSensitive()), this));
 	}
 
 	/**
@@ -70,8 +77,51 @@ public class DBTableAlias extends DefaultDBTable {
 	 *
 	 * @return	The aliased table.
 	 */
-	public DBTable getOriginTable(){
+	public DBTable getOriginTable() {
 		return originTable;
 	}
 
+	@Override
+	public Iterator<DBColumn> iterator() {
+		return columns.values().iterator();
+	}
+
+	@Override
+	public String getADQLSchemaName() {
+		return null;
+	}
+
+	@Override
+	public String getDBSchemaName() {
+		return null;
+	}
+
+	@Override
+	public String getADQLCatalogName() {
+		return null;
+	}
+
+	@Override
+	public String getDBCatalogName() {
+		return null;
+	}
+
+	@Override
+	public DBColumn getColumn(String colName, boolean byAdqlName) {
+		if (byAdqlName)
+			return columns.get(colName);
+		else {
+			for(DBColumn col : columns.values()) {
+				if (col.getDBName().equals(colName))
+					return col;
+			}
+			return null;
+		}
+	}
+
+	@Override
+	public DBTable copy(final String dbName, final String adqlName) {
+		return new DBTableAlias(originTable, adqlName);
+	}
+
 }
diff --git a/src/adql/db/DefaultDBColumn.java b/src/adql/db/DefaultDBColumn.java
index f262b6f5d74a148acd00dcb602d96ebf262e8624..78c2f359027affd53b17cb978979733037e95782 100644
--- a/src/adql/db/DefaultDBColumn.java
+++ b/src/adql/db/DefaultDBColumn.java
@@ -16,156 +16,116 @@ package adql.db;
  * 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,2015 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ * Copyright 2012-2019 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
  *                       Astronomisches Rechen Institut (ARI)
  */
 
 /**
  * Default implementation of {@link DBColumn}.
  *
+ * <p><i><b>WARNING: constructors signature and behavior changed since v2.0!</b>
+ * 	Before v2.0, the constructors expected to have the DB names before the ADQL
+ * 	names and thus, they forced to give a DB column name ; the ADQL column name
+ * 	being optional (if not provided it was set to the DB name).
+ * 	But since v2.0, this logic is inverted: the ADQL name is mandatory (a
+ * 	{@link NullPointerException} will be thrown if NULL or empty) while the DB
+ * 	name is optional ({@link #getDBName()} will return the same as
+ * 	{@link #getADQLName()} if no DB name is specified at initialization).
+ * 	Consequently, the ADQL names are expected as first parameters.
+ * </i></p>
+ *
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 1.4 (08/2015)
+ * @version 2.0 (09/2019)
  */
-public class DefaultDBColumn implements DBColumn {
+public class DefaultDBColumn extends DBIdentifier implements DBColumn {
 
-	/** Name of the column in the "database". */
-	protected String dbName;
 	/** Type of the column in the "database".
 	 * <i>Note: This should be one of the types listed by the IVOA in the TAP description.</i>
 	 * @since 1.3 */
 	protected DBType type;
+
 	/** Table in which this column exists. */
 	protected DBTable table;
-	/** Name that this column must have in ADQL queries. */
-	protected String adqlName = null;
-
-	/** Indicate whether the ADQL column name should be considered as case
-	 * sensitive.
-	 * @since 2.0 */
-	protected boolean columnCaseSensitive = false;
 
 	/**
-	 * Builds a default {@link DBColumn} with the given DB name and DB table.
+	 * Builds a default {@link DBColumn} with the given ADQL name and table.
+	 *
+	 * <p>With this constructor: DB name = ADQL name.</p>
 	 *
-	 * @param dbName	Database column name (it will be also used for the ADQL name).
-	 * 					<b>Only the column name is expected. Contrary to {@link DefaultDBTable},
-	 * 					if a whole column reference is given, no split will be done.</b>
-	 * @param table		DB table which contains this column.
+	 * @param adqlName	The ADQL name of this column (i.e. name to use in ADQL).
+	 * @param table		Table which contains this column.
 	 *
-	 * @see #DefaultDBColumn(String, String, DBType, DBTable)
+	 * @throws NullPointerException	If the given ADQL name is NULL or empty.
+	 *
+	 * @since 2.0
 	 */
-	public DefaultDBColumn(final String dbName, final DBTable table) {
-		this(dbName, dbName, null, table);
+	public DefaultDBColumn(final String adqlName, final DBTable table) throws NullPointerException {
+		this(adqlName, null, null, table);
 	}
 
 	/**
-	 * Builds a default {@link DBColumn} with the given DB name and DB table.
+	 * Builds a default {@link DBColumn} with the given ADQL name and table.
 	 *
-	 * @param dbName	Database column name (it will be also used for the ADQL name).
-	 * 					<b>Only the column name is expected. Contrary to {@link DefaultDBTable},
-	 * 					if a whole column reference is given, no split will be done.</b>
+	 * @param adqlName	The ADQL name of this column (i.e. name to use in ADQL).
 	 * @param type		Type of the column.
-	 *            		<i>Note: there is no default value. Consequently if this parameter is NULL,
-	 *            		the type should be considered as unknown. It means that any comparison with
-	 *            		any type will always return 'true'.</i>
-	 * @param table		DB table which contains this column.
+	 *            		<i><b>Note:</b> there is no default value. Consequently
+	 *            		if this parameter is NULL, the type should be considered
+	 *            		as unknown. It means that any comparison with any type
+	 *            		will always return <code>true</code>.</i>
+	 * @param table		Table which contains this column.
 	 *
-	 * @see #DefaultDBColumn(String, String, DBType, DBTable)
+	 * @throws NullPointerException	If the given ADQL name is NULL or empty.
 	 *
-	 * @since 1.3
+	 * @since 2.0
 	 */
-	public DefaultDBColumn(final String dbName, final DBType type, final DBTable table) {
-		this(dbName, dbName, type, table);
+	public DefaultDBColumn(final String adqlName, final DBType type, final DBTable table) throws NullPointerException {
+		this(adqlName, null, type, table);
 	}
 
 	/**
-	 * Builds a default {@link DBColumn} with the given DB name, DB table and ADQL name.
+	 * Builds a default {@link DBColumn} with the given ADQL and DB names and
+	 * table.
 	 *
-	 * @param dbName	Database column name.
-	 * 					<b>Only the column name is expected. Contrary to {@link DefaultDBTable},
-	 * 					if a whole column reference is given, no split will be done.</b>
-	 * @param adqlName	Column name used in ADQL queries.
-	 * 					<b>Only the column name is expected. Contrary to {@link DefaultDBTable},
-	 * 					if a whole column reference is given, no split will be done.</b>
-	 * @param table		DB table which contains this column.
+	 * @param adqlName	The ADQL name of this column (i.e. name to use in ADQL).
+	 * @param dbName	Database name.
+	 *                	<i>If NULL, {@link #getDBName()} will return the same as
+	 *                	{@link #getADQLName()}.</i>
+	 * @param table		Table which contains this column.
 	 *
-	 * @see #DefaultDBColumn(String, String, DBType, DBTable)
+	 * @throws NullPointerException	If the given ADQL name is NULL or empty.
+	 *
+	 * @since 2.0
 	 */
-	public DefaultDBColumn(final String dbName, final String adqlName, final DBTable table) {
-		this(dbName, adqlName, null, table);
+	public DefaultDBColumn(final String adqlName, final String dbName, final DBTable table) throws NullPointerException {
+		this(adqlName, dbName, null, table);
 	}
 
 	/**
-	 * Builds a default {@link DBColumn} with the given DB name, DB table and ADQL name.
-	 *
-	 * @param dbName	Database column name.
-	 * 					<b>Only the column name is expected. Contrary to {@link DefaultDBTable},
-	 * 					if a whole column reference is given, no split will be done.</b>
-	 *              	<b>REQUIRED parameter: it must be not NULL.</b>
-	 * @param adqlName	Column name used in ADQL queries.
-	 * 					<b>Only the column name is expected. Contrary to {@link DefaultDBTable},
-	 * 					if a whole column reference is given, no split will be done.</b>
-	 *                	<em>If NULL, it will be set to dbName.</em>
+	 * Builds a default {@link DBColumn} with the given ADQL and DB names, type
+	 * and table
+	 *
+	 * @param adqlName	The ADQL name of this column (i.e. name to use in ADQL).
+	 * @param dbName	Database name.
+	 *                	<i>If NULL, {@link #getDBName()} will return the same as
+	 *                	{@link #getADQLName()}.</i>
 	 * @param type		Type of the column.
-	 *            		<i>Note: there is no default value. Consequently if this parameter is NULL,
-	 *            		the type should be considered as unknown. It means that any comparison with
-	 *            		any type will always return 'true'.</i>
-	 * @param table		DB table which contains this column.
+	 *            		<i><b>Note:</b> there is no default value. Consequently
+	 *            		if this parameter is NULL, the type should be considered
+	 *            		as unknown. It means that any comparison with any type
+	 *            		will always return <code>true</code>.</i>
+	 * @param table		Table which contains this column.
 	 *
-	 * @since 1.3
+	 * @throws NullPointerException	If the given ADQL name is NULL or empty.
+	 *
+	 * @since 2.0
 	 */
-	public DefaultDBColumn(final String dbName, final String adqlName, final DBType type, final DBTable table) {
-
-		if (dbName == null || dbName.length() == 0)
-			throw new NullPointerException("Missing DB name!");
-
-		this.dbName = dbName;
-		setADQLName(adqlName);
+	public DefaultDBColumn(final String adqlName, final String dbName, final DBType type, final DBTable table) throws NullPointerException {
+		super(adqlName, dbName);
 
 		this.type = type;
 		this.table = table;
 	}
 
-	@Override
-	public final String getADQLName() {
-		return adqlName;
-	}
-
-	public final void setADQLName(String name) {
-		if (name != null) {
-
-			// Remove leading and trailing space characters:
-			name = name.trim();
-
-			// Detect automatically case sensitivity:
-			boolean caseSensitive = DefaultDBTable.isDelimited(name);
-			if (caseSensitive)
-				name = name.substring(1, name.length() - 1).replaceAll("\"\"", "\"");
-
-			// ONLY if the final name is NOT empty:
-			if (name.trim().length() > 0) {
-				adqlName = name;
-				columnCaseSensitive = caseSensitive;
-			}
-		}
-	}
-
-	@Override
-	public boolean isCaseSensitive() {
-		return columnCaseSensitive;
-	}
-
-	/**
-	 * Change the case sensitivity of the ADQL column name.
-	 *
-	 * @param sensitive	<code>true</code> to consider the current ADQL name as
-	 *                 	case sensitive,
-	 *                 	<code>false</code> otherwise.
-	 */
-	public void setCaseSensitive(final boolean sensitive) {
-		columnCaseSensitive = sensitive;
-	}
-
 	@Override
 	public final DBType getDatatype() {
 		return type;
@@ -192,11 +152,6 @@ public class DefaultDBColumn implements DBColumn {
 		this.type = type;
 	}
 
-	@Override
-	public final String getDBName() {
-		return dbName;
-	}
-
 	@Override
 	public final DBTable getTable() {
 		return table;
@@ -208,7 +163,7 @@ public class DefaultDBColumn implements DBColumn {
 
 	@Override
 	public DBColumn copy(final String dbName, final String adqlName, final DBTable dbTable) {
-		return new DefaultDBColumn(dbName, adqlName, type, dbTable);
+		return new DefaultDBColumn(adqlName, dbName, type, dbTable);
 	}
 
 }
diff --git a/src/adql/db/DefaultDBTable.java b/src/adql/db/DefaultDBTable.java
index fb89c046c08d46a2eb5a314a158fcb607195e92a..735e824ee8ea02ba787b31b25c4013656f1da082 100644
--- a/src/adql/db/DefaultDBTable.java
+++ b/src/adql/db/DefaultDBTable.java
@@ -28,34 +28,41 @@ import java.util.Map;
 /**
  * Default implementation of {@link DBTable}.
  *
+ * <p><i><b>WARNING: constructors signature and behavior changed since v2.0!</b>
+ * 	Before v2.0, the constructors expected to have the DB names before the ADQL
+ * 	names and thus, they forced to give a DB table name ; the ADQL table name
+ * 	being optional (if not provided it was set to the DB name).
+ * 	But since v2.0, this logic is inverted: the ADQL name is mandatory (a
+ * 	{@link NullPointerException} will be thrown if NULL or empty) while the DB
+ * 	name is optional ({@link #getDBName()} will return the same as
+ * 	{@link #getADQLName()} if no DB name is specified at initialization).
+ * 	Consequently, the ADQL names are expected as first parameters.
+ * </i></p>
+ *
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
  * @version 2.0 (09/2019)
  */
-public class DefaultDBTable implements DBTable {
+public class DefaultDBTable extends DBIdentifier implements DBTable {
 
 	protected String dbCatalogName = null;
 	protected String dbSchemaName = null;
-	protected String dbName;
 
 	protected String adqlCatalogName = null;
 	protected String adqlSchemaName = null;
-	protected String adqlName = null;
-
-	protected boolean tableCaseSensitive = false;
 
 	protected Map<String, DBColumn> columns = new LinkedHashMap<String, DBColumn>();
 
 	/**
-	 * Builds a default {@link DBTable} with the given DB name.
+	 * Builds a default {@link DBTable} with the given <b>ADQL name</b>.
 	 *
-	 * <p>With this constructor: ADQL name = DB name.</p>
+	 * <p>With this constructor: DB name = ADQL name.</p>
 	 *
 	 * <p><i><b>Note:</b>
 	 * 	The ADQL/DB schema and catalog names are set to NULL.
 	 * </i></p>
 	 *
 	 * <p><i><b>WARNING:</b>
-	 * 	The ADQL table name MUST NOT be qualified (i.e. prefixed by a schema
+	 * 	The ADQL table name MUST be NON-qualified (i.e. not prefixed by a schema
 	 * 	and/or a catalog)! For instance, <code>t1</code> is ok, but not
 	 * 	<code>schema1.t1</code> or <code>cat1.schema1.t2</code> which won't be
 	 * 	split but instead, considered as the whole ADQL name.
@@ -66,16 +73,18 @@ public class DefaultDBTable implements DBTable {
 	 * 	In such case, the surrounded name would be considered as case-sensitive.
 	 * </i></p>
 	 *
-	 * @param dbName	Database name (it will be also used as ADQL table name).
+	 * @param adqlName	The ADQL name of this table (i.e. name to use in ADQL).
 	 *
-	 * @see #DefaultDBTable(String, String)
+	 * @throws NullPointerException	If the given ADQL name is NULL or empty.
+	 *
+	 * @since 2.0
 	 */
-	public DefaultDBTable(final String dbName) {
-		this(dbName, null);
+	public DefaultDBTable(final String adqlName) throws NullPointerException {
+		super(adqlName);
 	}
 
 	/**
-	 * Builds a default {@link DBTable} with the given DB and ADQL names.
+	 * Builds a default {@link DBTable} with the given ADQL and DB names.
 	 *
 	 * <p><i><b>Note:</b>
 	 * 	The ADQL/DB schema and catalog names are set to NULL.
@@ -93,19 +102,22 @@ public class DefaultDBTable implements DBTable {
 	 * 	In such case, the surrounded name would be considered as case-sensitive.
 	 * </i></p>
 	 *
-	 * @param dbName	Database name.
 	 * @param adqlName	Name used in ADQL queries.
-	 *                	<i>If NULL, dbName will be used instead.</i>
+	 * @param dbName	Database name.
+	 *                	<i>If NULL, {@link #getDBName()} will return the same as
+	 *                	{@link #getADQLName()}.</i>
+	 *
+	 * @throws NullPointerException	If the given ADQL name is NULL or empty.
+	 *
+	 * @since 2.0
 	 */
-	public DefaultDBTable(final String dbName, final String adqlName) {
-		if (dbName == null || dbName.trim().length() == 0)
-			throw new NullPointerException("Missing DB name!");
-		this.dbName = dbName;
-		setADQLName(adqlName);
+	public DefaultDBTable(final String adqlName, final String dbName) throws NullPointerException {
+		super(adqlName, dbName);
 	}
 
 	/**
-	 * Builds default {@link DBTable} with a DB catalog, schema and table names.
+	 * Builds default {@link DBTable} with a ADQL catalog, schema and table
+	 * names.
 	 *
 	 * <p><i><b>WARNING:</b>
 	 * 	The ADQL table name MUST NOT be qualified (i.e. prefixed by a schema
@@ -119,22 +131,24 @@ public class DefaultDBTable implements DBTable {
 	 * 	In such case, the surrounded name would be considered as case-sensitive.
 	 * </i></p>
 	 *
-	 * @param dbCatName		Database catalog name (it will be also used as ADQL
-	 *                 		catalog name).
-	 * @param dbSchemaName	Database schema name (it will be also used as ADQL
-	 *                   	schema name).
-	 * @param dbName		Database table name (it will be also used as ADQL
-	 *              		table name).
-	 *                 		<em>MUST NOT be NULL!</em>
+	 * @param adqlCatName		ADQL catalog name (it will be also used as DB
+	 *                 			catalog name).
+	 * @param adqlSchemaName	ADQL schema name (it will be also used as DB
+	 *                   		schema name).
+	 * @param adqlName			ADQL table name (it will be also used as DB
+	 *              			table name).
+	 *                 			<i>MUST NOT be NULL!</i>
+	 *
+	 * @throws NullPointerException	If the given ADQL name is NULL or empty.
 	 *
-	 * @see #DefaultDBTable(String, String, String, String, String, String)
+	 * @since 2.0
 	 */
-	public DefaultDBTable(final String dbCatName, final String dbSchemaName, final String dbName) {
-		this(dbCatName, null, dbSchemaName, null, dbName, null);
+	public DefaultDBTable(final String adqlCatName, final String adqlSchemaName, final String adqlName) throws NullPointerException {
+		this(adqlCatName, null, adqlSchemaName, null, adqlName, null);
 	}
 
 	/**
-	 * Builds default {@link DBTable} with the DB and ADQL names for the
+	 * Builds default {@link DBTable} with the ADQL and DB names for the
 	 * catalog, schema and table.
 	 *
 	 * <p><i><b>WARNING:</b>
@@ -149,128 +163,45 @@ public class DefaultDBTable implements DBTable {
 	 * 	In such case, the surrounded name would be considered as case-sensitive.
 	 * </i></p>
 	 *
-	 * @param dbCatName			Database catalog name.
 	 * @param adqlCatName		Catalog name used in ADQL queries.
-	 *                   		<em>If NULL, it will be set to dbCatName.</em>
-	 * @param dbSchemaName		Database schema name.
+	 * @param dbCatName			Database catalog name.
+	 *                   		<i>If NULL, it will be set to adqlCatName.</i>
 	 * @param adqlSchemaName	Schema name used in ADQL queries.
-	 *                   		<em>If NULL, it will be set to dbSchemName.</em>
-	 * @param dbName			Database table name.
-	 *                 			<em>MUST NOT be NULL!</em>
+	 * @param dbSchemaName		Database schema name.
+	 *                   		<i>If NULL, it will be set to adqlSchemaName.</i>
 	 * @param adqlName			Table name used in ADQL queries.
-	 *                   		<em>If NULL, it will be set to dbName.</em>
+	 *                 			<i>MUST NOT be NULL!</i>
+	 * @param dbName			Database table name.
+	 *                   		<i>If NULL, it will be set to adqlName.</i>
+	 *
+	 * @throws NullPointerException	If the given ADQL name is NULL or empty.
 	 */
-	public DefaultDBTable(final String dbCatName, final String adqlCatName, final String dbSchemaName, final String adqlSchemaName, final String dbName, final String adqlName) {
-		if (dbName == null || dbName.trim().length() == 0)
-			throw new NullPointerException("Missing DB name!");
-
-		this.dbName = dbName;
-		setADQLName(adqlName);
-
-		this.dbSchemaName = dbSchemaName;
-		this.adqlSchemaName = (adqlSchemaName == null) ? dbSchemaName : adqlSchemaName;
+	public DefaultDBTable(final String adqlCatName, final String dbCatName, final String adqlSchemaName, final String dbSchemaName, final String adqlName, final String dbName) throws NullPointerException {
+		super(adqlName, dbName);
 
-		this.dbCatalogName = dbCatName;
-		this.adqlCatalogName = (adqlCatName == null) ? dbCatName : adqlCatName;
-	}
+		setADQLSchemaName(adqlSchemaName);
+		setDBSchemaName(dbSchemaName);
 
-	@Override
-	public final String getDBName() {
-		return dbName;
+		setADQLCatalogName(adqlCatName);
+		setDBCatalogName(dbCatName);
 	}
 
 	@Override
 	public final String getDBSchemaName() {
-		return dbSchemaName;
+		return (dbSchemaName == null) ? adqlSchemaName : dbSchemaName;
 	}
 
-	@Override
-	public final String getDBCatalogName() {
-		return dbCatalogName;
+	public final void setDBSchemaName(final String name) {
+		dbSchemaName = normalize(name);
 	}
 
 	@Override
-	public final String getADQLName() {
-		return adqlName;
-	}
-
-	/**
-	 * Change the ADQL name of this table.
-	 *
-	 * <p>
-	 * 	The case sensitivity is automatically set. The table name will be
-	 * 	considered as case sensitive if the given name is surrounded by double
-	 * 	quotes (<code>"</code>). In such case, the table name is stored and then
-	 * 	returned WITHOUT these double quotes.
-	 * </p>
-	 *
-	 * <p><i><b>WARNING:</b>
-	 * 	If the name without the double quotes (and then trimmed) is an empty
-	 * 	string, the ADQL name will be set to the {@link #getDBName()} as such.
-	 * 	Then the case sensitivity will be set to <code>false</code>.
-	 * </i></p>
-	 *
-	 * @param name	New ADQL name of this table.
-	 */
-	public void setADQLName(final String name) {
-		// Set the new table name (only if not NULL, otherwise use the DB name):
-		adqlName = (name != null) ? name : dbName;
-
-		// Detect automatically case sensitivity:
-		if ((tableCaseSensitive = isDelimited(adqlName)))
-			adqlName = adqlName.substring(1, adqlName.length() - 1).replaceAll("\"\"", "\"");
-
-		// If the final name is empty, no case sensitivity and use the DB name:
-		if (adqlName.trim().length() == 0) {
-			adqlName = dbName;
-			tableCaseSensitive = false;
-		}
-	}
-
-	/**
-	 * Tell whether the given identifier is delimited (i.e. within the same pair
-	 * of double quotes - <code>"</code>).
-	 *
-	 * <i>
-	 * <p>The following identifiers ARE delimited:</p>
-	 * <ul>
-	 * 	<li><code>"a"</code></li>
-	 * 	<li><code>" "</code> (string with spaces ; but won't be considered as a
-	 * 	                      valid ADQL name)</li>
-	 * 	<li><code>"foo.bar"</code></li>
-	 * 	<li><code>"foo"".""bar"</code> (with escaped double quotes)</li>
-	 * 	<li><code>""""</code> (idem)</li>
-	 * </ul>
-	 * </i>
-	 *
-	 * <i>
-	 * <p>The following identifiers are NOT considered as delimited:</p>
-	 * <ul>
-	 * 	<li><code>""</code> (empty string)</li>
-	 * 	<li><code>"foo</code> (missing ending double quote)</li>
-	 * 	<li><code>foo"</code> (missing leading double quote)</li>
-	 * 	<li><code>"foo"."bar"</code> (not the same pair of double quotes)</li>
-	 * </ul>
-	 * </i>
-	 *
-	 * @param name	Identifier that may be delimited.
-	 *
-	 * @return	<code>true</code> if the given identifier is delimited,
-	 *        	<code>false</code> otherwise.
-	 *
-	 * @since 2.0
-	 */
-	public static final boolean isDelimited(final String name) {
-		return name != null && name.matches("\"(\"\"|[^\"])*\"");
-	}
-
-	@Override
-	public boolean isCaseSensitive() {
-		return tableCaseSensitive;
+	public final String getDBCatalogName() {
+		return (dbCatalogName == null) ? adqlCatalogName : dbCatalogName;
 	}
 
-	public void setCaseSensitive(final boolean sensitive) {
-		tableCaseSensitive = sensitive;
+	public final void setDBCatalogName(final String name) {
+		dbCatalogName = normalize(name);
 	}
 
 	@Override
@@ -279,7 +210,7 @@ public class DefaultDBTable implements DBTable {
 	}
 
 	public void setADQLSchemaName(final String name) {
-		adqlSchemaName = (name != null) ? name : dbSchemaName;
+		adqlSchemaName = normalize(name);
 	}
 
 	@Override
@@ -288,7 +219,7 @@ public class DefaultDBTable implements DBTable {
 	}
 
 	public void setADQLCatalogName(final String name) {
-		adqlName = (name != null) ? null : dbName;
+		adqlCatalogName = normalize(dbName);
 	}
 
 	/**
@@ -423,8 +354,8 @@ public class DefaultDBTable implements DBTable {
 
 	@Override
 	public DBTable copy(String dbName, String adqlName) {
-		DefaultDBTable copy = new DefaultDBTable(dbCatalogName, adqlCatalogName, dbSchemaName, adqlSchemaName, dbName, adqlName);
-		copy.tableCaseSensitive = tableCaseSensitive;
+		DefaultDBTable copy = new DefaultDBTable(adqlCatalogName, dbCatalogName, adqlSchemaName, dbSchemaName, adqlName, dbName);
+		copy.setCaseSensitive(this.isCaseSensitive());
 		for(DBColumn col : this) {
 			if (col instanceof DBCommonColumn)
 				copy.addColumn(new DBCommonColumn((DBCommonColumn)col, col.getDBName(), col.getADQLName()));
diff --git a/src/adql/db/SearchColumnList.java b/src/adql/db/SearchColumnList.java
index 22c836099a21595ff36726ddd8ccb6b28097e408..c6169f88de29025f6bf199de823cba993fd329aa 100644
--- a/src/adql/db/SearchColumnList.java
+++ b/src/adql/db/SearchColumnList.java
@@ -2,21 +2,21 @@ package adql.db;
 
 /*
  * This file is part of ADQLLibrary.
- * 
+ *
  * ADQLLibrary is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Lesser General Public License as published by
  * the Free Software Foundation, either version 3 of the License, or
  * (at your option) any later version.
- * 
+ *
  * ADQLLibrary is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU Lesser General Public License for more details.
- * 
+ *
  * You should have received a copy of the GNU Lesser General Public License
  * along with ADQLLibrary.  If not, see <http://www.gnu.org/licenses/>.
- * 
- * Copyright 2012-2017 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *
+ * Copyright 2012-2019 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
  *                       Astronomisches Rechen Institut (ARI)
  */
 
@@ -34,20 +34,24 @@ import adql.query.operand.ADQLColumn;
 import cds.utils.TextualSearchList;
 
 /**
- * <p>A list of {@link DBColumn} elements ordered by their ADQL name in an ascending manner.</p>
- * 
+ * A list of {@link DBColumn} elements ordered by their ADQL name in an
+ * ascending manner.
+ *
  * <p>
- * 	In addition to an ADQL name, {@link DBColumn} elements can be searched by specifying their table, schema and catalog.
- * 	These last information will be used only if the ADQL column name is ambiguous, otherwise all matching elements are returned.
+ * 	In addition to an ADQL name, {@link DBColumn} elements can be searched by
+ * 	specifying their table, schema and catalog. These last information will be
+ * 	used only if the ADQL column name is ambiguous, otherwise all matching
+ * 	elements are returned.
  * </p>
- * 
- * <p><i>
- * 	<u>Note:</u>
- * 	Table aliases can be listed here with their corresponding table name. Consequently, a table alias can be given as table name in the search parameters.
+ *
+ * <p><i><b>Note:</b>
+ * 	Table aliases can be listed here with their corresponding table name.
+ * 	Consequently, a table alias can be given as table name in the search
+ * 	parameters.
  * </i></p>
- * 
+ *
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 1.4 (09/2017)
+ * @version 2.0 (09/2019)
  */
 public class SearchColumnList extends TextualSearchList<DBColumn> {
 	private static final long serialVersionUID = 1L;
@@ -56,10 +60,10 @@ public class SearchColumnList extends TextualSearchList<DBColumn> {
 	private boolean distinct = false;
 
 	/** Case-sensitive dictionary of table aliases. (tableAlias <-> TableName) */
-	private final Map<String,String> tableAliases = new HashMap<String,String>();
+	private final Map<String, String> tableAliases = new HashMap<String, String>();
 
 	/** Case-insensitive dictionary of table aliases. (tablealias <-> List&lt;TableName&gt;) */
-	private final Map<String,List<String>> mapAliases = new HashMap<String,List<String>>();
+	private final Map<String, List<String>> mapAliases = new HashMap<String, List<String>>();
 
 	/* ************ */
 	/* CONSTRUCTORS */
@@ -67,25 +71,25 @@ public class SearchColumnList extends TextualSearchList<DBColumn> {
 	/**
 	 * Void constructor.
 	 */
-	public SearchColumnList(){
+	public SearchColumnList() {
 		super(new DBColumnKeyExtractor());
 	}
 
 	/**
 	 * Constructor by copy: all the elements of the given collection of {@link DBColumn} are copied ordered into this list.
-	 * 
+	 *
 	 * @param collection	Collection of {@link DBColumn} to copy.
 	 */
-	public SearchColumnList(final Collection<DBColumn> collection){
+	public SearchColumnList(final Collection<DBColumn> collection) {
 		super(collection, new DBColumnKeyExtractor());
 	}
 
 	/**
 	 * Constructor with the initial capacity.
-	 * 
+	 *
 	 * @param initialCapacity	Initial capacity of this list.
 	 */
-	public SearchColumnList(final int initialCapacity){
+	public SearchColumnList(final int initialCapacity) {
 		super(initialCapacity, new DBColumnKeyExtractor());
 	}
 
@@ -94,19 +98,19 @@ public class SearchColumnList extends TextualSearchList<DBColumn> {
 	/* ******* */
 	/**
 	 * Tells whether multiple occurrences are allowed.
-	 * 
+	 *
 	 * @return <i>true</i> means that multiple occurrences are allowed, <i>false</i> otherwise.
 	 */
-	public final boolean isDistinct(){
+	public final boolean isDistinct() {
 		return distinct;
 	}
 
 	/**
 	 * Lets indicating that multiple occurrences are allowed.
-	 * 
+	 *
 	 * @param distinct <i>true</i> means that multiple occurrences are allowed, <i>false</i> otherwise.
 	 */
-	public final void setDistinct(final boolean distinct){
+	public final void setDistinct(final boolean distinct) {
 		this.distinct = distinct;
 	}
 
@@ -115,16 +119,16 @@ public class SearchColumnList extends TextualSearchList<DBColumn> {
 	/* ********************** */
 	/**
 	 * Adds the given association between a table name and its alias in a query.
-	 * 
+	 *
 	 * @param tableAlias	Table alias.
 	 * @param tableName		Table name.
 	 */
-	public final void putTableAlias(final String tableAlias, final String tableName){
-		if (tableAlias != null && tableName != null){
+	public final void putTableAlias(final String tableAlias, final String tableName) {
+		if (tableAlias != null && tableName != null) {
 			tableAliases.put(tableAlias, tableName);
 
 			List<String> aliases = mapAliases.get(tableAlias.toLowerCase());
-			if (aliases == null){
+			if (aliases == null) {
 				aliases = new ArrayList<String>();
 				mapAliases.put(tableAlias.toLowerCase(), aliases);
 			}
@@ -134,14 +138,14 @@ public class SearchColumnList extends TextualSearchList<DBColumn> {
 
 	/**
 	 * Removes the given alias from this list.
-	 * 
+	 *
 	 * @param tableAlias	The table alias which must be removed.
 	 */
-	public final void removeTableAlias(final String tableAlias){
+	public final void removeTableAlias(final String tableAlias) {
 		tableAliases.remove(tableAlias);
 
 		List<String> aliases = mapAliases.get(tableAlias.toLowerCase());
-		if (aliases != null){
+		if (aliases != null) {
 			aliases.remove(tableAlias);
 			if (aliases.isEmpty())
 				mapAliases.remove(tableAlias.toLowerCase());
@@ -151,12 +155,12 @@ public class SearchColumnList extends TextualSearchList<DBColumn> {
 	/**
 	 * Removes all table name/alias associations.
 	 */
-	public final void removeAllTableAliases(){
+	public final void removeAllTableAliases() {
 		tableAliases.clear();
 		mapAliases.clear();
 	}
 
-	public final int getNbTableAliases(){
+	public final int getNbTableAliases() {
 		return tableAliases.size();
 	}
 
@@ -165,71 +169,71 @@ public class SearchColumnList extends TextualSearchList<DBColumn> {
 	/* ************** */
 	/**
 	 * Searches all {@link DBColumn} elements which has the given name (case insensitive).
-	 * 
+	 *
 	 * @param columnName	ADQL name of {@link DBColumn} to search for.
-	 * 
+	 *
 	 * @return				The corresponding {@link DBColumn} elements.
-	 * 
+	 *
 	 * @see TextualSearchList#get(String)
 	 */
-	public List<DBColumn> search(final String columnName){
+	public List<DBColumn> search(final String columnName) {
 		return get(columnName);
 	}
 
 	/**
 	 * Searches all {@link DBColumn} elements which have the given catalog, schema, table and column name (case insensitive).
-	 * 
+	 *
 	 * @param catalog	Catalog name.
 	 * @param schema	Schema name.
 	 * @param table		Table name.
 	 * @param column	Column name.
-	 * 
+	 *
 	 * @return			The list of all matching {@link DBColumn} elements.
-	 * 
+	 *
 	 * @see #search(String, String, String, String, byte)
 	 */
-	public final List<DBColumn> search(final String catalog, final String schema, final String table, final String column){
+	public final List<DBColumn> search(final String catalog, final String schema, final String table, final String column) {
 		return search(catalog, schema, table, column, (byte)0);
 	}
 
 	/**
 	 * Searches all {@link DBColumn} elements corresponding to the given {@link ADQLColumn} (case insensitive).
-	 * 
+	 *
 	 * @param column	An {@link ADQLColumn}.
-	 * 
+	 *
 	 * @return			The list of all corresponding {@link DBColumn} elements.
-	 * 
+	 *
 	 * @see #search(String, String, String, String, byte)
 	 */
-	public List<DBColumn> search(final ADQLColumn column){
+	public List<DBColumn> search(final ADQLColumn column) {
 		return search(column.getCatalogName(), column.getSchemaName(), column.getTableName(), column.getColumnName(), column.getCaseSensitive());
 	}
 
 	/**
 	 * Searches all {@link DBColumn} elements which have the given catalog, schema, table and column name, with the specified case sensitivity.
-	 * 
+	 *
 	 * @param catalog			Catalog name.
 	 * @param schema			Schema name.
 	 * @param table				Table name.
 	 * @param column			Column name.
 	 * @param caseSensitivity	Case sensitivity for each column parts (one bit by part ; 0=sensitive,1=insensitive ; see {@link IdentifierField} for more details).
-	 * 
+	 *
 	 * @return					The list of all matching {@link DBColumn} elements.
-	 * 
+	 *
 	 * @see IdentifierField
 	 */
-	public List<DBColumn> search(final String catalog, final String schema, final String table, final String column, final byte caseSensitivity){
+	public List<DBColumn> search(final String catalog, final String schema, final String table, final String column, final byte caseSensitivity) {
 
 		List<DBColumn> tmpResult = get(column, IdentifierField.COLUMN.isCaseSensitive(caseSensitivity));
 
 		/* WITH TABLE PREFIX */
-		if (table != null){
+		if (table != null) {
 			/* 1. Figure out the table alias */
 			String tableName = null;
 			List<String> aliasMatches = null;
 
 			// Case sensitive => tableName is set , aliasMatches = null
-			if (IdentifierField.TABLE.isCaseSensitive(caseSensitivity)){
+			if (IdentifierField.TABLE.isCaseSensitive(caseSensitivity)) {
 				tableName = tableAliases.get(table);
 				if (tableName == null)
 					tableName = table;
@@ -237,7 +241,7 @@ public class SearchColumnList extends TextualSearchList<DBColumn> {
 			// Case INsensitive
 			// a) Alias is found => tableName = null  , aliasMatches contains the list of all tables matching the alias
 			// b) No alias       => tableName = table , aliasMatches = null
-			else{
+			else {
 				aliasMatches = mapAliases.get(table.toLowerCase());
 				if (aliasMatches == null || aliasMatches.isEmpty())
 					tableName = table;
@@ -246,7 +250,7 @@ public class SearchColumnList extends TextualSearchList<DBColumn> {
 			/* 2. For each found column, test whether its table, schema and catalog names match.
 			 *    If it matches, keep the column aside. */
 			ArrayList<DBColumn> result = new ArrayList<DBColumn>();
-			for(DBColumn match : tmpResult){
+			for(DBColumn match : tmpResult) {
 
 				// Get the list of all tables covered by this column:
 				//   - only 1 if it is a normal column
@@ -259,23 +263,23 @@ public class SearchColumnList extends TextualSearchList<DBColumn> {
 
 				// Test the matching with every covered tables:
 				DBTable matchTable;
-				while(itMatchTables.hasNext()){
+				while(itMatchTables.hasNext()) {
 					// get the table:
 					matchTable = itMatchTables.next();
 
 					// test the table name:
-					if (aliasMatches == null){	// case table name is (sensitive) or (INsensitive with no alias found)
-						if (IdentifierField.TABLE.isCaseSensitive(caseSensitivity)){
+					if (aliasMatches == null) {	// case table name is (sensitive) or (INsensitive with no alias found)
+						if (IdentifierField.TABLE.isCaseSensitive(caseSensitivity)) {
 							if (!matchTable.getADQLName().equals(tableName))
 								continue;
-						}else{
+						} else {
 							if (!matchTable.getADQLName().equalsIgnoreCase(tableName))
 								continue;
 						}
-					}else{	// case INsensitive with at least one alias found
+					} else {	// case INsensitive with at least one alias found
 						boolean foundAlias = false;
 						String temp;
-						for(int a = 0; !foundAlias && a < aliasMatches.size(); a++){
+						for(int a = 0; !foundAlias && a < aliasMatches.size(); a++) {
 							temp = tableAliases.get(aliasMatches.get(a));
 							if (temp != null)
 								foundAlias = matchTable.getADQLName().equalsIgnoreCase(temp);
@@ -285,27 +289,27 @@ public class SearchColumnList extends TextualSearchList<DBColumn> {
 					}
 
 					// test the schema name:
-					if (schema != null){
+					if (schema != null) {
 						// No schema name (<=> no schema), then this table can not be a good match:
 						if (matchTable.getADQLSchemaName() == null)
 							continue;
-						if (IdentifierField.SCHEMA.isCaseSensitive(caseSensitivity)){
+						if (IdentifierField.SCHEMA.isCaseSensitive(caseSensitivity)) {
 							if (!matchTable.getADQLSchemaName().equals(schema))
 								continue;
-						}else{
+						} else {
 							if (!matchTable.getADQLSchemaName().equalsIgnoreCase(schema))
 								continue;
 						}
 
 						// test the catalog name:
-						if (catalog != null){
+						if (catalog != null) {
 							// No catalog name (<=> no catalog), then this table can not be a good match:
 							if (matchTable.getADQLCatalogName() == null)
 								continue;
-							if (IdentifierField.CATALOG.isCaseSensitive(caseSensitivity)){
+							if (IdentifierField.CATALOG.isCaseSensitive(caseSensitivity)) {
 								if (!matchTable.getADQLCatalogName().equals(catalog))
 									continue;
-							}else{
+							} else {
 								if (!matchTable.getADQLCatalogName().equalsIgnoreCase(catalog))
 									continue;
 							}
@@ -321,15 +325,15 @@ public class SearchColumnList extends TextualSearchList<DBColumn> {
 
 		}
 		/* NO TABLE PREFIX */
-		else{
+		else {
 			// Special case: the columns merged by a NATURAL JOIN or a USING may have no table reference:
-			if (tmpResult.size() > 1){
+			if (tmpResult.size() > 1) {
 				// List all common columns. If there are several, only the list of matching normal columns must be returned.
 				// This list must not contain common columns.
 				// Instead, it must contains all normal columns covered by the common columns.
 				ArrayList<DBColumn> result = new ArrayList<DBColumn>(tmpResult.size());
-				for(int i = 0; i < tmpResult.size(); i++){
-					if (ADQLJoin.isCommonColumn(tmpResult.get(i))){
+				for(int i = 0; i < tmpResult.size(); i++) {
+					if (ADQLJoin.isCommonColumn(tmpResult.get(i))) {
 						// this common column is a good match
 						// => add it into the list of matching common columns
 						//    AND remove it from the normal columns list
@@ -354,7 +358,7 @@ public class SearchColumnList extends TextualSearchList<DBColumn> {
 	/* INHERITED METHODS */
 	/* ***************** */
 	@Override
-	public boolean add(final DBColumn item){
+	public boolean add(final DBColumn item) {
 		if (distinct && contains(item))
 			return false;
 		else
@@ -362,13 +366,13 @@ public class SearchColumnList extends TextualSearchList<DBColumn> {
 	}
 
 	@Override
-	public boolean addAll(final Collection<? extends DBColumn> c){
+	public boolean addAll(final Collection<? extends DBColumn> c) {
 		boolean changed = super.addAll(c);
 
-		if (changed){
-			if (c instanceof SearchColumnList){
+		if (changed) {
+			if (c instanceof SearchColumnList) {
 				SearchColumnList list = (SearchColumnList)c;
-				for(Map.Entry<String,String> entry : list.tableAliases.entrySet())
+				for(Map.Entry<String, String> entry : list.tableAliases.entrySet())
 					putTableAlias(entry.getKey(), entry.getValue());
 			}
 		}
@@ -377,11 +381,11 @@ public class SearchColumnList extends TextualSearchList<DBColumn> {
 	}
 
 	@Override
-	public boolean removeAll(final Collection<?> c){
+	public boolean removeAll(final Collection<?> c) {
 		boolean changed = super.removeAll(c);
 
-		if (changed){
-			if (c instanceof SearchColumnList){
+		if (changed) {
+			if (c instanceof SearchColumnList) {
 				SearchColumnList list = (SearchColumnList)c;
 				for(String key : list.tableAliases.keySet())
 					removeTableAlias(key);
@@ -393,50 +397,53 @@ public class SearchColumnList extends TextualSearchList<DBColumn> {
 
 	/**
 	 * Lets extracting the key to associate with a given {@link DBColumn} instance.
-	 * 
+	 *
 	 * @author Gr&eacute;gory Mantelet (CDS)
-	 * @version 09/2011
+	 * @version 2.0 (09/2019)
 	 */
 	private static class DBColumnKeyExtractor implements KeyExtractor<DBColumn> {
 		@Override
-		public String getKey(DBColumn obj){
-			return obj.getADQLName();
+		public String getKey(DBColumn obj) {
+			if (obj.isCaseSensitive())
+				return obj.getADQLName();
+			else
+				return obj.getADQLName().toLowerCase();
 		}
 	}
 
 	/**
 	 * Iterator that iterates over only one item, given in the constructor.
-	 * 
+	 *
 	 * @param <E> Type of the item that this Iterator must return.
-	 * 
+	 *
 	 * @author Gr&eacute;gory Mantelet (ARI) - gmantele@ari.uni-heidelberg.de
 	 * @version 1.2 (11/2013)
 	 * @since 1.2
 	 */
-	private static class SingleIterator< E > implements Iterator<E> {
+	private static class SingleIterator<E> implements Iterator<E> {
 		private final E item;
 		private boolean done = false;
 
-		public SingleIterator(final E singleItem){
+		public SingleIterator(final E singleItem) {
 			item = singleItem;
 		}
 
 		@Override
-		public boolean hasNext(){
+		public boolean hasNext() {
 			return !done;
 		}
 
 		@Override
-		public E next(){
-			if (!done){
+		public E next() {
+			if (!done) {
 				done = true;
 				return item;
-			}else
+			} else
 				throw new NoSuchElementException();
 		}
 
 		@Override
-		public void remove(){
+		public void remove() {
 			throw new UnsupportedOperationException();
 		}
 	}
diff --git a/src/adql/db/SearchTableApi.java b/src/adql/db/SearchTableApi.java
index c7d047abd30d3e298edcecf946e2d3a4c8a22374..f92a76a4a99879931d458a3be050f12a1a30c9b0 100644
--- a/src/adql/db/SearchTableApi.java
+++ b/src/adql/db/SearchTableApi.java
@@ -2,21 +2,22 @@ package adql.db;
 
 /*
  * This file is part of ADQLLibrary.
- * 
+ *
  * ADQLLibrary is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Lesser General Public License as published by
  * the Free Software Foundation, either version 3 of the License, or
  * (at your option) any later version.
- * 
+ *
  * ADQLLibrary is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU Lesser General Public License for more details.
- * 
+ *
  * 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 2017 - Astronomisches Rechen Institut (ARI)
+ *
+ * Copyright 2017-2019 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ *                       Astronomisches Rechen Institut (ARI)
  */
 
 import java.util.List;
@@ -26,22 +27,36 @@ import adql.query.from.ADQLTable;
 /**
  * Simple interface about a class which allows to search for a specified
  * {@link ADQLTable}.
- * 
- * @author Gr&eacute;gory Mantelet (ARI)
- * @version 1.4 (09/2017)
+ *
+ * @author Gr&eacute;gory Mantelet (ARI;CDS)
+ * @version 2.0 (09/2019)
  * @since 1.4
- * 
+ *
  * @see SearchTableList
  */
 public interface SearchTableApi {
 
 	/**
 	 * Searches all {@link DBTable} elements corresponding to the given {@link ADQLTable} (case insensitive).
-	 * 
+	 *
 	 * @param table	An {@link ADQLTable}.
-	 * 
+	 *
 	 * @return		The list of all corresponding {@link DBTable} elements.
 	 */
 	public List<DBTable> search(final ADQLTable table);
 
+	/**
+	 * Adds the given object at the end of this list.
+	 *
+	 * @param obj	Object to add (different from NULL).
+	 *
+	 * @throws NullPointerException		If the given object or its extracted key is <code>null</code>.
+	 * @throws IllegalArgumentException	If the extracted key is already used by another object in this list.
+	 *
+	 * @see java.util.ArrayList#add(java.lang.Object)
+	 */
+	public boolean add(final DBTable item);
+
+	public SearchTableApi getCopy();
+
 }
\ No newline at end of file
diff --git a/src/adql/db/SearchTableList.java b/src/adql/db/SearchTableList.java
index 8522d24bf851d0766ce6e932c8537cd3777219f2..80cc7afcfcf7a94325f42f42c33ad5fb6c7a4364 100644
--- a/src/adql/db/SearchTableList.java
+++ b/src/adql/db/SearchTableList.java
@@ -2,21 +2,21 @@ package adql.db;
 
 /*
  * This file is part of ADQLLibrary.
- * 
+ *
  * ADQLLibrary is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Lesser General Public License as published by
  * the Free Software Foundation, either version 3 of the License, or
  * (at your option) any later version.
- * 
+ *
  * ADQLLibrary is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU Lesser General Public License for more details.
- * 
+ *
  * You should have received a copy of the GNU Lesser General Public License
  * along with ADQLLibrary.  If not, see <http://www.gnu.org/licenses/>.
- * 
- * Copyright 2012-2017 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ *
+ * Copyright 2012-2019 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
  *                       Astronomisches Rechen Institut (ARI)
  */
 
@@ -29,15 +29,18 @@ import adql.query.from.ADQLTable;
 import cds.utils.TextualSearchList;
 
 /**
- * <p>A list of {@link DBTable} elements ordered by their ADQL name in an ascending manner.</p>
- * 
+ * A list of {@link DBTable} elements ordered by their ADQL name in an ascending
+ * manner.
+ *
  * <p>
- * 	In addition to an ADQL name, {@link DBTable} elements can be searched by specifying their schema and catalog.
- * 	These last information will be used only if the ADQL table name is ambiguous, otherwise all matching elements are returned.
+ * 	In addition to an ADQL name, {@link DBTable} elements can be searched by
+ * 	specifying their schema and catalog. These last information will be used
+ * 	only if the ADQL table name is ambiguous, otherwise all matching elements
+ * 	are returned.
  * </p>
- * 
+ *
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 1.4 (09/2017)
+ * @version 2.0 (09/2019)
  */
 public class SearchTableList extends TextualSearchList<DBTable> implements SearchTableApi {
 	private static final long serialVersionUID = 1L;
@@ -51,25 +54,25 @@ public class SearchTableList extends TextualSearchList<DBTable> implements Searc
 	/**
 	 * Void constructor.
 	 */
-	public SearchTableList(){
+	public SearchTableList() {
 		super(new DBTableKeyExtractor());
 	}
 
 	/**
 	 * Constructor by copy: all the elements of the given collection of {@link DBTable} are copied ordered into this list.
-	 * 
+	 *
 	 * @param collection	Collection of {@link DBTable} to copy.
 	 */
-	public SearchTableList(final Collection<? extends DBTable> collection){
+	public SearchTableList(final Collection<? extends DBTable> collection) {
 		super(collection, new DBTableKeyExtractor());
 	}
 
 	/**
 	 * Constructor with the initial capacity.
-	 * 
+	 *
 	 * @param initialCapacity	Initial capacity of this list.
 	 */
-	public SearchTableList(final int initialCapacity){
+	public SearchTableList(final int initialCapacity) {
 		super(initialCapacity, new DBTableKeyExtractor());
 	}
 
@@ -78,105 +81,101 @@ public class SearchTableList extends TextualSearchList<DBTable> implements Searc
 	/* ******* */
 	/**
 	 * Tells whether multiple occurrences are allowed.
-	 * 
+	 *
 	 * @return <i>true</i> means that multiple occurrences are allowed, <i>false</i> otherwise.
 	 */
-	public final boolean isDistinct(){
+	public final boolean isDistinct() {
 		return distinct;
 	}
 
 	/**
 	 * Lets indicating that multiple occurrences are allowed.
-	 * 
+	 *
 	 * @param distinct <i>true</i> means that multiple occurrences are allowed, <i>false</i> otherwise.
 	 */
-	public final void setDistinct(final boolean distinct){
+	public final void setDistinct(final boolean distinct) {
 		this.distinct = distinct;
 	}
 
+	@Override
+	public SearchTableApi getCopy() {
+		return new SearchTableList(this);
+	}
+
 	/* ************** */
 	/* SEARCH METHODS */
 	/* ************** */
 	/**
 	 * Searches all {@link DBTable} elements which has the given name (case insensitive).
-	 * 
+	 *
 	 * @param tableName	ADQL name of {@link DBTable} to search for.
-	 * 
+	 *
 	 * @return			The corresponding {@link DBTable} elements.
-	 * 
+	 *
 	 * @see TextualSearchList#get(String)
 	 */
-	public List<DBTable> search(final String tableName){
+	public List<DBTable> search(final String tableName) {
 		return get(tableName);
 	}
 
 	/**
 	 * Searches all {@link DBTable} elements which have the given catalog, schema, and table name (case insensitive).
-	 * 
+	 *
 	 * @param catalog	Catalog name.
 	 * @param schema	Schema name.
 	 * @param table		Table name.
-	 * 
+	 *
 	 * @return			The list of all matching {@link DBTable} elements.
-	 * 
+	 *
 	 * @see #search(String, String, String, byte)
 	 */
-	public final List<DBTable> search(final String catalog, final String schema, final String table){
+	public final List<DBTable> search(final String catalog, final String schema, final String table) {
 		return search(catalog, schema, table, (byte)0);
 	}
 
-	/**
-	 * Searches all {@link DBTable} elements corresponding to the given {@link ADQLTable} (case insensitive).
-	 * 
-	 * @param table	An {@link ADQLTable}.
-	 * 
-	 * @return		The list of all corresponding {@link DBTable} elements.
-	 * 
-	 * @see #search(String, String, String, byte)
-	 */
 	@Override
-	public List<DBTable> search(final ADQLTable table){
+	public List<DBTable> search(final ADQLTable table) {
 		return search(table.getCatalogName(), table.getSchemaName(), table.getTableName(), table.getCaseSensitive());
 	}
 
 	/**
 	 * Searches all {@link DBTable} elements which have the given catalog, schema, and table name, with the specified case sensitivity.
-	 * 
+	 *
 	 * @param catalog			Catalog name.
 	 * @param schema			Schema name.
 	 * @param table				Table name.
 	 * @param caseSensitivity	Case sensitivity for each table parts (one bit by part ; 0=sensitive,1=insensitive ; see {@link IdentifierField} for more details).
-	 * 
+	 *
 	 * @return					The list of all matching {@link DBTable} elements.
-	 * 
+	 *
 	 * @see IdentifierField
 	 */
-	public List<DBTable> search(final String catalog, final String schema, final String table, final byte caseSensitivity){
+	public List<DBTable> search(final String catalog, final String schema, final String table, final byte caseSensitivity) {
 		List<DBTable> tmpResult = get(table, IdentifierField.TABLE.isCaseSensitive(caseSensitivity));
 
-		if (schema != null){
+		if (schema != null) {
 			List<DBTable> result = new ArrayList<DBTable>();
 
-			for(DBTable match : tmpResult){
+			for(DBTable match : tmpResult) {
 				// No schema name (<=> no schema), then this table can not be a good match:
 				if (match.getADQLSchemaName() == null)
 					continue;
-				if (IdentifierField.SCHEMA.isCaseSensitive(caseSensitivity)){
+				if (IdentifierField.SCHEMA.isCaseSensitive(caseSensitivity)) {
 					if (!match.getADQLSchemaName().equals(schema))
 						continue;
-				}else{
+				} else {
 					if (!match.getADQLSchemaName().equalsIgnoreCase(schema))
 						continue;
 				}
 
-				if (catalog != null){
+				if (catalog != null) {
 					// No catalog name (<=> no catalog), then this table can not be a good match:
 					if (match.getADQLCatalogName() == null)
 						continue;
-					if (IdentifierField.CATALOG.isCaseSensitive(caseSensitivity)){
+					if (IdentifierField.CATALOG.isCaseSensitive(caseSensitivity)) {
 						if (!match.getADQLCatalogName().equals(catalog))
 							continue;
-					}else{
+					} else {
 						if (!match.getADQLCatalogName().equalsIgnoreCase(catalog))
 							continue;
 					}
@@ -186,7 +185,7 @@ public class SearchTableList extends TextualSearchList<DBTable> implements Searc
 			}
 			return result;
 
-		}else
+		} else
 			return tmpResult;
 	}
 
@@ -194,7 +193,7 @@ public class SearchTableList extends TextualSearchList<DBTable> implements Searc
 	/* INHERITED METHODS */
 	/* ***************** */
 	@Override
-	public boolean add(final DBTable item){
+	public boolean add(final DBTable item) {
 		if (distinct && contains(item))
 			return false;
 		else
@@ -203,14 +202,17 @@ public class SearchTableList extends TextualSearchList<DBTable> implements Searc
 
 	/**
 	 * Lets extracting a key to associate with a given {@link DBTable} instance.
-	 * 
+	 *
 	 * @author Gr&eacute;gory Mantelet (CDS)
-	 * @version 09/2011
+	 * @version 2.0 (09/2019)
 	 */
 	private static class DBTableKeyExtractor implements KeyExtractor<DBTable> {
 		@Override
-		public String getKey(DBTable obj){
-			return obj.getADQLName();
+		public String getKey(DBTable obj) {
+			if (obj.isCaseSensitive())
+				return obj.getADQLName();
+			else
+				return obj.getADQLName().toLowerCase();
 		}
 	}
 
diff --git a/src/adql/parser/ADQLQueryFactory.java b/src/adql/parser/ADQLQueryFactory.java
index e6558e66cdfc57526aa691244c53de881ee09c95..4305fdee0f9c178e52b9644962b0e9f838b48730 100644
--- a/src/adql/parser/ADQLQueryFactory.java
+++ b/src/adql/parser/ADQLQueryFactory.java
@@ -33,6 +33,7 @@ import adql.query.ColumnReference;
 import adql.query.IdentifierField;
 import adql.query.SelectItem;
 import adql.query.TextPosition;
+import adql.query.WithItem;
 import adql.query.constraint.ADQLConstraint;
 import adql.query.constraint.Between;
 import adql.query.constraint.Comparison;
@@ -207,6 +208,13 @@ public class ADQLQueryFactory {
 		}
 	}
 
+	/** @since 2.0 */
+	public WithItem createWithItem(final IdentifierItem queryLabel, final ADQLQuery query, final Collection<ADQLColumn> colLabels) throws Exception {
+		WithItem item = new WithItem(queryLabel.identifier, query, colLabels);
+		item.setLabelCaseSensitive(queryLabel.caseSensitivity);
+		return item;
+	}
+
 	public SelectItem createSelectItem(ADQLOperand operand, String alias) throws Exception {
 		return new SelectItem(operand, alias);
 	}
diff --git a/src/adql/parser/feature/FeatureSet.java b/src/adql/parser/feature/FeatureSet.java
index e01d6e319175a86d8fd43c71909788884d35d674..da3073f0393036e003ee849a954a827b732453b5 100644
--- a/src/adql/parser/feature/FeatureSet.java
+++ b/src/adql/parser/feature/FeatureSet.java
@@ -29,6 +29,7 @@ import java.util.Set;
 
 import adql.db.FunctionDef;
 import adql.query.ClauseOffset;
+import adql.query.WithItem;
 import adql.query.constraint.ComparisonOperator;
 import adql.query.operand.BitNotOperand;
 import adql.query.operand.OperationType;
@@ -583,8 +584,6 @@ public class FeatureSet implements Iterable<LanguageFeature> {
 	public static final LanguageFeature EXCEPT = new LanguageFeature(FeatureType.ADQL_SETS, "EXCEPT"); // TODO EXCEPT
 	public static final LanguageFeature INTERSECT = new LanguageFeature(FeatureType.ADQL_SETS, "INTERSECT");  // TODO INTERSECT
 	
-	public static final LanguageFeature WITH = new LanguageFeature(FeatureType.ADQL_COMMON_TABLE, "WITH");  // TODO WITH
-	
 	public static final LanguageFeature CAST = new LanguageFeature(FeatureType.ADQL_TYPE, "CAST");  // TODO CAST*/
 
 	/** All standard features available.
@@ -597,7 +596,7 @@ public class FeatureSet implements Iterable<LanguageFeature> {
 	 * <p><i><b>Important note:</b>
 	 * 	All of them must be optional and must have a type.
 	 * </i></p> */
-	static LanguageFeature[] availableFeatures = new LanguageFeature[]{ InUnitFunction.FEATURE, BitNotOperand.FEATURE, OperationType.BIT_AND.getFeatureDescription(), OperationType.BIT_OR.getFeatureDescription(), OperationType.BIT_XOR.getFeatureDescription(), ClauseOffset.FEATURE, ComparisonOperator.ILIKE.getFeatureDescription(), LowerFunction.FEATURE, AreaFunction.FEATURE, BoxFunction.FEATURE, CentroidFunction.FEATURE, CircleFunction.FEATURE, ContainsFunction.FEATURE, ExtractCoord.FEATURE_COORD1, ExtractCoord.FEATURE_COORD2, ExtractCoordSys.FEATURE, DistanceFunction.FEATURE, IntersectsFunction.FEATURE, PointFunction.FEATURE, PolygonFunction.FEATURE, RegionFunction.FEATURE };
+	static LanguageFeature[] availableFeatures = new LanguageFeature[]{ WithItem.FEATURE, InUnitFunction.FEATURE, BitNotOperand.FEATURE, OperationType.BIT_AND.getFeatureDescription(), OperationType.BIT_OR.getFeatureDescription(), OperationType.BIT_XOR.getFeatureDescription(), ClauseOffset.FEATURE, ComparisonOperator.ILIKE.getFeatureDescription(), LowerFunction.FEATURE, AreaFunction.FEATURE, BoxFunction.FEATURE, CentroidFunction.FEATURE, CircleFunction.FEATURE, ContainsFunction.FEATURE, ExtractCoord.FEATURE_COORD1, ExtractCoord.FEATURE_COORD2, ExtractCoordSys.FEATURE, DistanceFunction.FEATURE, IntersectsFunction.FEATURE, PointFunction.FEATURE, PolygonFunction.FEATURE, RegionFunction.FEATURE };
 
 	/**
 	 * List all available language features.
diff --git a/src/adql/parser/grammar/adqlGrammar200.jj b/src/adql/parser/grammar/adqlGrammar200.jj
index d1e6b4a78806e6e59c7355ceeb42749b5340c335..360c11cdf5c36b28a849910f8ae7c728dbf8a695 100644
--- a/src/adql/parser/grammar/adqlGrammar200.jj
+++ b/src/adql/parser/grammar/adqlGrammar200.jj
@@ -29,7 +29,7 @@
 * If the syntax is not conform to the ADQL definition a TokenMgrError or a
 * ParseException is thrown.
 *
-*  Author:  Gr&eacute;gory Mantelet (CDS)
+* Author:  Gr&eacute;gory Mantelet (CDS)
 *  Version: 2.0 (10/2020)
 */
 
@@ -102,11 +102,11 @@ import adql.query.operand.function.geometry.GeometryFunction.GeometryValue;
  * </i></p>
 * 
  * @see ADQLParser
-*
-* @author Gr&eacute;gory Mantelet (CDS)
+ *
+ * @author Gr&eacute;gory Mantelet (CDS)
 * @version 2.0 (10/2020)
-* @since 2.0
-*/
+ * @since 2.0
+ */
 public class ADQLGrammar200 extends ADQLGrammarBase {
 
 	/* **********************************************************************
@@ -482,7 +482,7 @@ void Select(): {ClauseSelect select = query.getSelect(); SelectItem item=null; T
 	}
 }
 
-SelectItem SelectItem(): {IdentifierItems identifiers = new IdentifierItems(true); IdentifierItem id = null, label = null; ADQLOperand op = null; SelectItem item; Token starToken;} {
+SelectItem SelectItem(): {IdentifierItems identifiers = new IdentifierItems(true); IdentifierItem firstID = null, id = null, label = null; ADQLOperand op = null; SelectItem item; Token starToken;} {
 	(
 		( starToken=<ASTERISK>
 		  {
@@ -493,7 +493,7 @@ SelectItem SelectItem(): {IdentifierItems identifiers = new IdentifierItems(true
 		)
 	|LOOKAHEAD(7)
 		(
-			id=Identifier() <DOT> { identifiers.append(id); }
+			id=Identifier() <DOT> { identifiers.append(id); firstID = id; }
 			(
 				id=Identifier() <DOT> { identifiers.append(id); }
 				(
@@ -503,9 +503,11 @@ SelectItem SelectItem(): {IdentifierItems identifiers = new IdentifierItems(true
 			starToken=<ASTERISK>
 			{
 				try{
-					item = new SelectAllColumns( queryFactory.createTable(identifiers, null) );
-					TextPosition firstPos = identifiers.get(0).position;
-					item.setPosition(new TextPosition(firstPos.beginLine, firstPos.beginColumn, starToken.endLine, (starToken.endColumn < 0) ? -1 : (starToken.endColumn + 1)));
+					ADQLTable table = queryFactory.createTable(identifiers, null);
+					table.setPosition(new TextPosition(firstID.position, id.position));
+					
+					item = new SelectAllColumns(table);
+					item.setPosition(new TextPosition(firstID.position, new TextPosition(starToken)));
 					return item;
 				}catch(Exception ex) {
 					throw generateParseException(ex);
diff --git a/src/adql/parser/grammar/adqlGrammar201.jj b/src/adql/parser/grammar/adqlGrammar201.jj
index 7ee135a2f5a80d3c0ad37017e8bbae4b48f494f1..ffceab0ffe6ad8cc35080dd7b12243db7c23cf95 100644
--- a/src/adql/parser/grammar/adqlGrammar201.jj
+++ b/src/adql/parser/grammar/adqlGrammar201.jj
@@ -31,7 +31,7 @@
 * ParseException is thrown.
 *
 * Author:  Gr&eacute;gory Mantelet (CDS)
-* Version: 2.0 (08/2019)
+* Version: 2.0 (09/2019)
 */
 
 							/* ########### */
@@ -107,7 +107,7 @@ import adql.query.operand.function.geometry.GeometryFunction.GeometryValue;
  * @see ADQLParser
  *
  * @author Gr&eacute;gory Mantelet (CDS)
- * @version 2.0 (08/2019)
+ * @version 2.0 (09/2019)
  * @since 2.0
  */
 public class ADQLGrammar201 extends ADQLGrammarBase {
@@ -206,7 +206,7 @@ SKIP : { < " " | "\t" | "\n" | "\r" | "\r\n" > }
 /* ************************************************************************** */
 
 TOKEN : {
-	< SQL_RESERVED_WORD: ("ABSOLUTE"|"ACTION"|"ADD"|"ALLOCATE"|"ALTER"|"ANY"|"ARE"|"ASSERTION"|"AT"|"AUTHORIZATION"|"BEGIN"|"BIT"|"BIT_LENGTH"|"BOTH"|"CASCADE"|"CASCADED"|"CASE"|"CAST"|"CATALOG"|"CHAR"|"CHARACTER"|"CHAR_LENGTH"|"CHARACTER_LENGTH"|"CHECK"|"CLOSE"|"COALESCE"|"COLLATE"|"COLLATION"|"COLUMN"|"COMMIT"|"CONNECT"|"CONNECTION"|"CONSTRAINT"|"CONSTRAINTS"|"CONTINUE"|"CONVERT"|"CORRESPONDING"|"CREATE"|"CURRENT"|"CURRENT_DATE"|"CURRENT_TIME"|"CURRENT_TIMESTAMP"|"CURRENT_USER"|"CURSOR"|"DATE"|"DAY"|"DEALLOCATE"|"DECIMAL"|"DECLARE"|"DEFAULT"|"DEFERRABLE"|"DEFERRED"|"DELETE"|"DESCRIBE"|"DESCRIPTOR"|"DIAGNOSTICS"|"DISCONNECT"|"DOMAIN"|"DOUBLE"|"DROP"|"ELSE"|"END"|"END-EXEC"|"ESCAPE"|"EXCEPT"|"EXCEPTION"|"EXEC"|"EXECUTE"|"EXTERNAL"|"EXTRACT"|"FALSE"|"FETCH"|"FIRST"|"FLOAT"|"FOR"|"FOREIGN"|"FOUND"|"GET"|"GLOBAL"|"GO"|"GOTO"|"GRANT"|"HOUR"|"IDENTITY"|"IMMEDIATE"|"INDICATOR"|"INITIALLY"|"INPUT"|"INSENSITIVE"|"INSERT"|"INT"|"INTEGER"|"INTERSECT"|"INTERVAL"|"INTO"|"ISOLATION"|"KEY"|"LANGUAGE"|"LAST"|"LEADING"|"LEVEL"|"LOCAL"|"MATCH"|"MINUTE"|"MODULE"|"MONTH"|"NAMES"|"NATIONAL"|"NCHAR"|"NEXT"|"NO"|"NULLIF"|"NUMERIC"|"OCTET_LENGTH"|"OF"|"ONLY"|"OPEN"|"OPTION"|"OUTPUT"|"OVERLAPS"|"PAD"|"PARTIAL"|"POSITION"|"PRECISION"|"PREPARE"|"PRESERVE"|"PRIMARY"|"PRIOR"|"PRIVILEGES"|"PROCEDURE"|"PUBLIC"|"READ"|"REAL"|"REFERENCES"|"RELATIVE"|"RESTRICT"|"REVOKE"|"ROLLBACK"|"ROWS"|"SCHEMA"|"SCROLL"|"SECOND"|"SECTION"|"SESSION"|"SESSION_USER"|"SET"|"SIZE"|"SMALLINT"|"SOME"|"SPACE"|"SQL"|"SQLCODE"|"SQLERROR"|"SQLSTATE"|"SUBSTRING"|"SYSTEM_USER"|"TABLE"|"TEMPORARY"|"THEN"|"TIME"|"TIMESTAMP"|"TIMEZONE_HOUR"|"TIMEZONE_MINUTE"|"TO"|"TRAILING"|"TRANSACTION"|"TRANSLATE"|"TRANSLATION"|"TRIM"|"TRUE"|"UNION"|"UNIQUE"|"UNKNOWN"|"UPDATE"|"UPPER"|"USAGE"|"USER"|"VALUE"|"VALUES"|"VARCHAR"|"VARYING"|"VIEW"|"WHEN"|"WHENEVER"|"WITH"|"WORK"|"WRITE"|"YEAR"|"ZONE") >
+	< SQL_RESERVED_WORD: ("ABSOLUTE"|"ACTION"|"ADD"|"ALLOCATE"|"ALTER"|"ANY"|"ARE"|"ASSERTION"|"AT"|"AUTHORIZATION"|"BEGIN"|"BIT"|"BIT_LENGTH"|"BOTH"|"CASCADE"|"CASCADED"|"CASE"|"CAST"|"CATALOG"|"CHAR"|"CHARACTER"|"CHAR_LENGTH"|"CHARACTER_LENGTH"|"CHECK"|"CLOSE"|"COALESCE"|"COLLATE"|"COLLATION"|"COLUMN"|"COMMIT"|"CONNECT"|"CONNECTION"|"CONSTRAINT"|"CONSTRAINTS"|"CONTINUE"|"CONVERT"|"CORRESPONDING"|"CREATE"|"CURRENT"|"CURRENT_DATE"|"CURRENT_TIME"|"CURRENT_TIMESTAMP"|"CURRENT_USER"|"CURSOR"|"DATE"|"DAY"|"DEALLOCATE"|"DECIMAL"|"DECLARE"|"DEFAULT"|"DEFERRABLE"|"DEFERRED"|"DELETE"|"DESCRIBE"|"DESCRIPTOR"|"DIAGNOSTICS"|"DISCONNECT"|"DOMAIN"|"DOUBLE"|"DROP"|"ELSE"|"END"|"END-EXEC"|"ESCAPE"|"EXCEPT"|"EXCEPTION"|"EXEC"|"EXECUTE"|"EXTERNAL"|"EXTRACT"|"FALSE"|"FETCH"|"FIRST"|"FLOAT"|"FOR"|"FOREIGN"|"FOUND"|"GET"|"GLOBAL"|"GO"|"GOTO"|"GRANT"|"HOUR"|"IDENTITY"|"IMMEDIATE"|"INDICATOR"|"INITIALLY"|"INPUT"|"INSENSITIVE"|"INSERT"|"INT"|"INTEGER"|"INTERSECT"|"INTERVAL"|"INTO"|"ISOLATION"|"KEY"|"LANGUAGE"|"LAST"|"LEADING"|"LEVEL"|"LOCAL"|"MATCH"|"MINUTE"|"MODULE"|"MONTH"|"NAMES"|"NATIONAL"|"NCHAR"|"NEXT"|"NO"|"NULLIF"|"NUMERIC"|"OCTET_LENGTH"|"OF"|"ONLY"|"OPEN"|"OPTION"|"OUTPUT"|"OVERLAPS"|"PAD"|"PARTIAL"|"POSITION"|"PRECISION"|"PREPARE"|"PRESERVE"|"PRIMARY"|"PRIOR"|"PRIVILEGES"|"PROCEDURE"|"PUBLIC"|"READ"|"REAL"|"REFERENCES"|"RELATIVE"|"RESTRICT"|"REVOKE"|"ROLLBACK"|"ROWS"|"SCHEMA"|"SCROLL"|"SECOND"|"SECTION"|"SESSION"|"SESSION_USER"|"SET"|"SIZE"|"SMALLINT"|"SOME"|"SPACE"|"SQL"|"SQLCODE"|"SQLERROR"|"SQLSTATE"|"SUBSTRING"|"SYSTEM_USER"|"TABLE"|"TEMPORARY"|"THEN"|"TIME"|"TIMESTAMP"|"TIMEZONE_HOUR"|"TIMEZONE_MINUTE"|"TO"|"TRAILING"|"TRANSACTION"|"TRANSLATE"|"TRANSLATION"|"TRIM"|"TRUE"|"UNION"|"UNIQUE"|"UNKNOWN"|"UPDATE"|"UPPER"|"USAGE"|"USER"|"VALUE"|"VALUES"|"VARCHAR"|"VARYING"|"VIEW"|"WHEN"|"WHENEVER"|"WORK"|"WRITE"|"YEAR"|"ZONE") >
 	{ matchedToken.sqlReserved = true; }
 }
 
@@ -305,7 +305,8 @@ TOKEN : {
 /* Other clauses' tokens */
 /* ********************* */
 TOKEN : {
-	< BY: "BY" >         { matchedToken.adqlReserved = true; }
+	< WITH: "WITH" >     { matchedToken.adqlReserved = true; }
+|	< BY: "BY" >         { matchedToken.adqlReserved = true; }
 |	< GROUP: "GROUP" >   { matchedToken.adqlReserved = true; }
 |	< HAVING: "HAVING" > { matchedToken.adqlReserved = true; }
 |	< ORDER: "ORDER" >   { matchedToken.adqlReserved = true; }
@@ -452,6 +453,7 @@ ADQLQuery QueryExpression(): {TextPosition endPos = null;} {
 			throw generateParseException(ex);
 		}
 	}
+	[With()]
 	Select()
 	From()     {endPos = query.getFrom().getPosition();}
 	[Where()   {endPos = query.getWhere().getPosition();}]
@@ -461,7 +463,10 @@ ADQLQuery QueryExpression(): {TextPosition endPos = null;} {
 	[Offset()  {endPos = new TextPosition(token);}]
 	{
 		// set the position of the query:
-		query.setPosition(new TextPosition(query.getSelect().getPosition(), endPos));
+		if (query.getWith().isEmpty())
+			query.setPosition(new TextPosition(query.getSelect().getPosition(), endPos));
+		else
+			query.setPosition(new TextPosition(query.getWith().getPosition(), endPos));
 		
 		// get the previous query (!= null if the current query is a sub-query):
 		ADQLQuery previousQuery = stackQuery.pop();
@@ -482,6 +487,33 @@ ADQLQuery SubQueryExpression(): {ADQLQuery q = null; Token start, end;} {
 	}
 }
 
+ClauseADQL<WithItem> With(): { ClauseADQL<WithItem> withClause = query.getWith(); WithItem withItem; Token start,end; IdentifierItem id, colId; ArrayList<ADQLColumn> colLabels = new ArrayList<ADQLColumn>(10); ADQLQuery query; } {
+	try {
+		start=<WITH> id=Identifier() [<LEFT_PAR> colId=Identifier() { colLabels.add(queryFactory.createColumn(colId)); } (<COMMA> colId=Identifier() { colLabels.add(queryFactory.createColumn(colId)); })* <RIGHT_PAR>] <AS> <LEFT_PAR> query=QueryExpression() end=<RIGHT_PAR>
+		{
+			withItem = queryFactory.createWithItem(id, query, colLabels);
+			withItem.setPosition(new TextPosition(id.position, new TextPosition(end)));
+			withClause.add(withItem);
+			colLabels.clear();
+		}
+		(
+			<COMMA> id=Identifier() [<LEFT_PAR> colId=Identifier() { colLabels.add(queryFactory.createColumn(colId)); } (<COMMA> colId=Identifier() { colLabels.add(queryFactory.createColumn(colId)); })* <RIGHT_PAR>] <AS> <LEFT_PAR> query=QueryExpression() end=<RIGHT_PAR>
+			{
+				withItem = queryFactory.createWithItem(id, query, colLabels);
+				withItem.setPosition(new TextPosition(id.position, new TextPosition(end)));
+				withClause.add(withItem);
+				colLabels.clear();
+			}
+		)*
+		{
+		  withClause.setPosition(new TextPosition(start, end));
+		  return withClause;
+		}
+	}catch(Exception ex) {
+		throw generateParseException(ex);
+	}
+}
+
 void Select(): {ClauseSelect select = query.getSelect(); SelectItem item=null; Token start,t = null;} {
 	start=<SELECT>
 	[t=<QUANTIFIER> {select.setDistinctColumns(t.image.equalsIgnoreCase("DISTINCT"));}]
@@ -503,7 +535,7 @@ void Select(): {ClauseSelect select = query.getSelect(); SelectItem item=null; T
 	}
 }
 
-SelectItem SelectItem(): {IdentifierItems identifiers = new IdentifierItems(true); IdentifierItem id = null, label = null; ADQLOperand op = null; SelectItem item; Token starToken;} {
+SelectItem SelectItem(): {IdentifierItems identifiers = new IdentifierItems(true); IdentifierItem firstID = null, id = null, label = null; ADQLOperand op = null; SelectItem item; Token starToken;} {
 	(
 		( starToken=<ASTERISK>
 		  {
@@ -514,7 +546,7 @@ SelectItem SelectItem(): {IdentifierItems identifiers = new IdentifierItems(true
 		)
 	|LOOKAHEAD(7)
 		(
-			id=Identifier() <DOT> { identifiers.append(id); }
+			id=Identifier() <DOT> { identifiers.append(id); firstID = id; }
 			(
 				id=Identifier() <DOT> { identifiers.append(id); }
 				(
@@ -524,9 +556,11 @@ SelectItem SelectItem(): {IdentifierItems identifiers = new IdentifierItems(true
 			starToken=<ASTERISK>
 			{
 				try{
-					item = new SelectAllColumns( queryFactory.createTable(identifiers, null) );
-					TextPosition firstPos = identifiers.get(0).position;
-					item.setPosition(new TextPosition(firstPos.beginLine, firstPos.beginColumn, starToken.endLine, (starToken.endColumn < 0) ? -1 : (starToken.endColumn + 1)));
+					ADQLTable table = queryFactory.createTable(identifiers, null);
+					table.setPosition(new TextPosition(firstID.position, id.position));
+					
+					item = new SelectAllColumns(table);
+					item.setPosition(new TextPosition(firstID.position, new TextPosition(starToken)));
 					return item;
 				}catch(Exception ex) {
 					throw generateParseException(ex);
diff --git a/src/adql/query/ADQLList.java b/src/adql/query/ADQLList.java
index 26cc05c8beccec3aa9a6374d48222680fa982cfb..465bb7a905321cc4ab1a5bf4114a21ffe5002882 100644
--- a/src/adql/query/ADQLList.java
+++ b/src/adql/query/ADQLList.java
@@ -34,7 +34,7 @@ import adql.parser.feature.LanguageFeature;
  * </p>
  *
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 2.0 (07/2019)
+ * @version 2.0 (08/2019)
  *
  * @see ClauseADQL
  * @see ClauseConstraints
@@ -63,7 +63,27 @@ public abstract class ADQLList<T extends ADQLObject> implements ADQLObject, Iter
 	 *
 	 * @param name	Prefix/Name of this list.
 	 */
-	protected ADQLList(String name) {
+	protected ADQLList(final String name) {
+		this(name, null);
+	}
+
+	/**
+	 * Builds an ADQLList with only its name and its corresponding language
+	 * feature.
+	 *
+	 * <p>The given name will always prefix the list.</p>
+	 *
+	 * <p>
+	 * 	The language feature is optional. If omitted, a default non-optional
+	 * 	one will be created using the list's name.
+	 * </p>
+	 *
+	 * @param name					Prefix/Name of this list.
+	 * @param implementedFeature	Language Feature implemented by this list.
+	 *
+	 * @since 2.0
+	 */
+	protected ADQLList(String name, final LanguageFeature implementedFeature) {
 		if (name != null) {
 			name = name.trim();
 			if (name.length() == 0)
@@ -72,7 +92,7 @@ public abstract class ADQLList<T extends ADQLObject> implements ADQLObject, Iter
 
 		this.name = name;
 
-		this.FEATURE = new LanguageFeature(null, "CLAUSE" + (this.name == null ? "" : "_" + this.name), false, "An ADQL clause (e.g. SELECT, FROM, ...).");
+		this.FEATURE = (implementedFeature != null) ? implementedFeature : new LanguageFeature(null, "CLAUSE" + (this.name == null ? "" : "_" + this.name), false, "An ADQL clause (e.g. SELECT, FROM, ...).");
 	}
 
 	/**
@@ -85,7 +105,7 @@ public abstract class ADQLList<T extends ADQLObject> implements ADQLObject, Iter
 	 */
 	@SuppressWarnings("unchecked")
 	protected ADQLList(ADQLList<T> toCopy) throws Exception {
-		this(toCopy.getName());
+		this(toCopy.getName(), toCopy.getFeatureDescription());
 		for(T obj : toCopy)
 			add((T)obj.getCopy());
 		position = (toCopy.position != null) ? new TextPosition(toCopy.position) : null;
diff --git a/src/adql/query/ADQLQuery.java b/src/adql/query/ADQLQuery.java
index 303b75f5714858dec2c04a6f0049444b764640be..02e92628f56fd422b66e09c937cb30512301ee96 100644
--- a/src/adql/query/ADQLQuery.java
+++ b/src/adql/query/ADQLQuery.java
@@ -25,6 +25,7 @@ import java.util.Iterator;
 import java.util.NoSuchElementException;
 
 import adql.db.DBColumn;
+import adql.db.DBIdentifier;
 import adql.db.DBType;
 import adql.db.DBType.DBDatatype;
 import adql.db.DefaultDBColumn;
@@ -64,6 +65,10 @@ public class ADQLQuery implements ADQLObject {
 	 * @since 2.0 */
 	private final ADQLVersion adqlVersion;
 
+	/** The ADQL clause WITH.
+	 * @since 2.0 */
+	private ClauseADQL<WithItem> with;
+
 	/** The ADQL clause SELECT. */
 	private ClauseSelect select;
 
@@ -110,6 +115,7 @@ public class ADQLQuery implements ADQLObject {
 	 */
 	public ADQLQuery(final ADQLVersion version) {
 		this.adqlVersion = (version == null ? ADQLParser.DEFAULT_VERSION : version);
+		with = new ClauseADQL<WithItem>("WITH");
 		select = new ClauseSelect();
 		from = null;
 		where = new ClauseConstraints("WHERE");
@@ -129,6 +135,7 @@ public class ADQLQuery implements ADQLObject {
 	@SuppressWarnings("unchecked")
 	public ADQLQuery(ADQLQuery toCopy) throws Exception {
 		adqlVersion = toCopy.adqlVersion;
+		with = (ClauseADQL<WithItem>)toCopy.with.getCopy();
 		select = (ClauseSelect)toCopy.select.getCopy();
 		from = (FromContent)toCopy.from.getCopy();
 		where = (ClauseConstraints)toCopy.where.getCopy();
@@ -157,6 +164,8 @@ public class ADQLQuery implements ADQLObject {
 	 * Clear all the clauses.
 	 */
 	public void reset() {
+		with.clear();
+
 		select.clear();
 		select.setDistinctColumns(false);
 		select.setNoLimit();
@@ -170,6 +179,38 @@ public class ADQLQuery implements ADQLObject {
 		position = null;
 	}
 
+	/**
+	 * Gets the WITH clause of this query.
+	 *
+	 * @return	Its WITH clause.
+	 *
+	 * @since 2.0
+	 */
+	public final ClauseADQL<WithItem> getWith() {
+		return with;
+	}
+
+	/**
+	 * Replaces its WITH clause by the given one.
+	 *
+	 * <p><i><b>Note:</b>
+	 * 	The position of the query is erased.
+	 * </i></p>
+	 *
+	 * @param newWith	The new WITH clause.
+	 *
+	 * @throws NullPointerException	If the given WITH clause is NULL.
+	 *
+	 * @since 2.0
+	 */
+	public void setWith(ClauseADQL<WithItem> newWith) throws NullPointerException {
+		if (newWith == null)
+			throw new NullPointerException("Impossible to replace the WITH clause of a query by NULL!");
+		else
+			with = newWith;
+		position = null;
+	}
+
 	/**
 	 * Gets the SELECT clause of this query.
 	 *
@@ -416,34 +457,38 @@ public class ADQLQuery implements ADQLObject {
 					else
 						columns.addAll(from.getDBColumns());
 				} catch(ParseException pe) {
-					// Here, this error should not occur any more, since it must have been caught by the DBChecker!
+					/* Here, this error should not occur any more, since it must
+					 * have been caught by the DBChecker! */
 				}
 			} else {
 				// Create the DBColumn:
 				DBColumn col = null;
 				// ...whose the name will be set with the SELECT item's alias:
 				if (item.hasAlias()) {
-					// put the alias in lower case if not written between "":
-					/* Note: This aims to avoid unexpected behavior at execution
-					 *       time in the DBMS (i.e. the case sensitivity is
-					 *       forced for every references to this column alias). */
-					String alias = item.getAlias();
-					if (!item.isCaseSensitive())
-						alias = alias.toLowerCase();
+					String alias;
+
+					// If delimited, put the alias between double quotes.
+					if (item.isCaseSensitive())
+						alias = DBIdentifier.denormalize(item.getAlias(), true);
+					// If not delimited, put the alias in lower-case.
+					else
+						alias = item.getAlias().toLowerCase();
 
 					// create the DBColumn:
 					if (operand instanceof ADQLColumn && ((ADQLColumn)operand).getDBLink() != null) {
 						col = ((ADQLColumn)operand).getDBLink();
-						col = col.copy(col.getDBName(), alias, col.getTable());
+						col = col.copy(alias, alias, col.getTable());
 					} else
 						col = new DefaultDBColumn(alias, null);
 				}
 				// ...or whose the name will be the name of the SELECT item:
 				else {
-					if (operand instanceof ADQLColumn && ((ADQLColumn)operand).getDBLink() != null)
-						col = ((ADQLColumn)operand).getDBLink();
-					else
-						col = new DefaultDBColumn(item.getName(), null);
+					if (operand instanceof ADQLColumn && ((ADQLColumn)operand).getDBLink() != null) {
+						DBColumn formerCol = ((ADQLColumn)operand).getDBLink();
+						// keep the same ADQL and DB name ; just change the table:
+						col = formerCol.copy(formerCol.getDBName(), (formerCol.isCaseSensitive() ? DBIdentifier.denormalize(formerCol.getADQLName(), true) : formerCol.getADQLName().toLowerCase()), formerCol.getTable());
+					} else
+						col = new DefaultDBColumn((item.isCaseSensitive() ? DBIdentifier.denormalize(item.getName(), true) : item.getName().toLowerCase()), null);
 				}
 
 				/* For columns created by default (from functions and operations generally),
@@ -505,24 +550,27 @@ public class ADQLQuery implements ADQLObject {
 				index++;
 				switch(index) {
 					case 0:
-						currentClause = select;
+						currentClause = with;
 						break;
 					case 1:
+						currentClause = select;
+						break;
+					case 2:
 						currentClause = null;
 						return from;
-					case 2:
+					case 3:
 						currentClause = where;
 						break;
-					case 3:
+					case 4:
 						currentClause = groupBy;
 						break;
-					case 4:
+					case 5:
 						currentClause = having;
 						break;
-					case 5:
+					case 6:
 						currentClause = orderBy;
 						break;
-					case 6:
+					case 7:
 						currentClause = null;
 						return offset;
 					default:
@@ -533,7 +581,7 @@ public class ADQLQuery implements ADQLObject {
 
 			@Override
 			public boolean hasNext() {
-				return index + 1 < 7;
+				return index + 1 < 8;
 			}
 
 			@Override
@@ -547,42 +595,48 @@ public class ADQLQuery implements ADQLObject {
 				else {
 					switch(index) {
 						case 0:
+							if (replacer instanceof ClauseADQL)
+								with = (ClauseADQL<WithItem>)replacer;
+							else
+								throw new UnsupportedOperationException("Impossible to replace a ClauseADQL (" + with.toADQL() + ") by a " + replacer.getClass().getName() + " (" + replacer.toADQL() + ")!");
+							break;
+						case 1:
 							if (replacer instanceof ClauseSelect)
 								select = (ClauseSelect)replacer;
 							else
 								throw new UnsupportedOperationException("Impossible to replace a ClauseSelect (" + select.toADQL() + ") by a " + replacer.getClass().getName() + " (" + replacer.toADQL() + ")!");
 							break;
-						case 1:
+						case 2:
 							if (replacer instanceof FromContent)
 								from = (FromContent)replacer;
 							else
 								throw new UnsupportedOperationException("Impossible to replace a FromContent (" + from.toADQL() + ") by a " + replacer.getClass().getName() + " (" + replacer.toADQL() + ")!");
 							break;
-						case 2:
+						case 3:
 							if (replacer instanceof ClauseConstraints)
 								where = (ClauseConstraints)replacer;
 							else
 								throw new UnsupportedOperationException("Impossible to replace a ClauseConstraints (" + where.toADQL() + ") by a " + replacer.getClass().getName() + " (" + replacer.toADQL() + ")!");
 							break;
-						case 3:
+						case 4:
 							if (replacer instanceof ClauseADQL)
 								groupBy = (ClauseADQL<ADQLOperand>)replacer;
 							else
 								throw new UnsupportedOperationException("Impossible to replace a ClauseADQL (" + groupBy.toADQL() + ") by a " + replacer.getClass().getName() + " (" + replacer.toADQL() + ")!");
 							break;
-						case 4:
+						case 5:
 							if (replacer instanceof ClauseConstraints)
 								having = (ClauseConstraints)replacer;
 							else
 								throw new UnsupportedOperationException("Impossible to replace a ClauseConstraints (" + having.toADQL() + ") by a " + replacer.getClass().getName() + " (" + replacer.toADQL() + ")!");
 							break;
-						case 5:
+						case 6:
 							if (replacer instanceof ClauseADQL)
 								orderBy = (ClauseADQL<ADQLOrder>)replacer;
 							else
 								throw new UnsupportedOperationException("Impossible to replace a ClauseADQL (" + orderBy.toADQL() + ") by a " + replacer.getClass().getName() + " (" + replacer.toADQL() + ")!");
 							break;
-						case 6:
+						case 7:
 							if (replacer instanceof ClauseOffset)
 								offset = (ClauseOffset)replacer;
 							else
@@ -598,13 +652,14 @@ public class ADQLQuery implements ADQLObject {
 				if (index <= -1)
 					throw new IllegalStateException("remove() impossible: next() has not yet been called!");
 
-				if (index == 0 || index == 1)
-					throw new UnsupportedOperationException("Impossible to remove a " + ((index == 0) ? "SELECT" : "FROM") + " clause from a query!");
-				else if (index == 6) {
+				if (index == 1 || index == 2)
+					throw new UnsupportedOperationException("Impossible to remove a " + ((index == 1) ? "SELECT" : "FROM") + " clause from a query!");
+				else if (index == 7) {
 					offset = null;
 					position = null;
 				} else {
-					currentClause.clear();
+					if (currentClause != null)
+						currentClause.clear();
 					position = null;
 				}
 			}
@@ -613,7 +668,13 @@ public class ADQLQuery implements ADQLObject {
 
 	@Override
 	public String toADQL() {
-		StringBuffer adql = new StringBuffer(select.toADQL());
+		StringBuffer adql = new StringBuffer();
+
+		if (!with.isEmpty())
+			adql.append(with.toADQL()).append('\n');
+
+		adql.append(select.toADQL());
+
 		adql.append("\nFROM ").append(from.toADQL());
 
 		if (!where.isEmpty())
diff --git a/src/adql/query/ClauseADQL.java b/src/adql/query/ClauseADQL.java
index 56d3ffbdc414c253c28a3bc91e2225ec46b9003d..a49c8d11cac702532c2f3b9246f1d700637630bd 100644
--- a/src/adql/query/ClauseADQL.java
+++ b/src/adql/query/ClauseADQL.java
@@ -16,34 +16,53 @@ 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-2013 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ * Copyright 2012-2019 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
  *                       Astronomisches Rechen Institut (ARI)
  */
 
+import adql.parser.feature.LanguageFeature;
+
 /**
  * Represents an ADQL clause (i.e. SELECT, FROM, WHERE, ...).
  *
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 1.2 (12/2013)
+ * @version 2.0 (08/2019)
  */
 public class ClauseADQL<T extends ADQLObject> extends ADQLList<T> {
 
 	/**
-	 * Builds an anonymous ClauseADQL.
+	 * Builds an anonymous {@link ClauseADQL}.
 	 */
 	public ClauseADQL() {
 		super((String)null);
 	}
 
 	/**
-	 * Builds a ClauseADQL considering its name.
+	 * Builds a {@link ClauseADQL} considering its name.
 	 *
-	 * @param name	List label.
+	 * @param name	Clause label.
 	 */
 	public ClauseADQL(String name) {
 		super(name);
 	}
 
+	/**
+	 * Builds a {@link ClauseADQL} considering its name.
+	 *
+	 * <p>
+	 * 	The language feature is optional. If omitted, a default non-optional
+	 * 	one will be created using the list's name.
+	 * </p>
+	 *
+	 * @param name					Clause label.
+	 * @param implementedFeature	Language Feature implemented by this list.
+	 *
+	 * @since 2.0
+	 */
+	protected ClauseADQL(final String name, final LanguageFeature implementedFeature) {
+		super(name, implementedFeature);
+	}
+
 	/**
 	 * Builds a ClauseADQL by copying the given one. It copies also all the list
 	 * items of the given ClauseADQL.
diff --git a/src/adql/query/ColumnReference.java b/src/adql/query/ColumnReference.java
index 07b21edcfbdacfb61e5d5d3abfec956d259d8b9c..6b2706fe575d9f305865a5fae860a2f90a15ee2e 100644
--- a/src/adql/query/ColumnReference.java
+++ b/src/adql/query/ColumnReference.java
@@ -187,7 +187,10 @@ public class ColumnReference implements ADQLObject {
 	 * @return 	Its source table if {@link #getColumnName()} is a column name
 	 *        	(not an alias),
 	 *        	or NULL otherwise.
+	 *
+	 * @deprecated	Since v2.0. This function is never used.
 	 */
+	@Deprecated
 	public final ADQLTable getAdqlTable() {
 		return adqlTable;
 	}
@@ -204,7 +207,10 @@ public class ColumnReference implements ADQLObject {
 	 * @param adqlTable	Its source table if {@link #getColumnName()} is a column
 	 *                 	name (not an alias),
 	 *                 	or NULL otherwise.
+	 *
+	 * @deprecated	Since v2.0. This piece of information is never used.
 	 */
+	@Deprecated
 	public final void setAdqlTable(ADQLTable adqlTable) {
 		this.adqlTable = adqlTable;
 	}
diff --git a/src/adql/query/WithItem.java b/src/adql/query/WithItem.java
new file mode 100644
index 0000000000000000000000000000000000000000..8300bdb37d3f060be599f3cd357b3794211b2fc4
--- /dev/null
+++ b/src/adql/query/WithItem.java
@@ -0,0 +1,353 @@
+package adql.query;
+
+/*
+ * This file is part of ADQLLibrary.
+ *
+ * ADQLLibrary is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * ADQLLibrary is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * 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 2019 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ */
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+import adql.db.DBColumn;
+import adql.db.DBIdentifier;
+import adql.db.DBTable;
+import adql.parser.feature.LanguageFeature;
+import adql.query.operand.ADQLColumn;
+
+/**
+ * Object representation of the definition of a Common Table Expression (CTE).
+ *
+ * <p>
+ * 	A such table is defined inside the ADQL clause <code>WITH</code>. It must
+ * 	be an ADQL query with a name for the resulting temporary table. Labels of
+ * 	the resulting columns may be also provided.
+ * </p>
+ *
+ * @author Gr&eacute;gory Mantelet (CDS)
+ * @version 2.0 (09/2019)
+ * @since 2.0
+ */
+public class WithItem implements ADQLObject {
+
+	/** Description of this ADQL Feature. */
+	public final static LanguageFeature FEATURE = new LanguageFeature(LanguageFeature.TYPE_ADQL_COMMON_TABLE, "WITH", true, "A Common Table Expression lets create a temporary named result set that can be referred to elsewhere in a main query.");
+
+	/** Name of the resulting table. */
+	protected String label;
+
+	/** Flag indicating whether the table name is case sensitive or not. */
+	protected boolean caseSensitive = false;
+
+	/** Labels of the resulting columns.
+	 * <p><i><b>Note:</b>
+	 * 	If NULL or empty, the default output column names must be used.
+	 * </i></p> */
+	protected List<ADQLColumn> columnLabels = null;
+
+	/** ADQL query providing the CTE's content. */
+	protected ADQLQuery query;
+
+	/** Position of this WITH item in the original ADQL query. */
+	protected TextPosition position = null;
+
+	/** Database description of the resulting (temporary) table. */
+	protected DBTable dbLink = null;
+
+	/**
+	 * Create a WITH item with the minimum mandatory information.
+	 *
+	 * @param label	Name of the resulting table/CTE.
+	 * @param query	ADQL query returning the content of this CTE.
+	 */
+	public WithItem(final String label, final ADQLQuery query) {
+		this(label, query, null);
+	}
+
+	/**
+	 * Create a WITH item with column labels.
+	 *
+	 * <p><i><b>Warning:</b>
+	 * 	If the given list is NULL or empty, the default output column names will
+	 * 	be used. However, if not NULL, the given list should contain as many
+	 * 	elements as columns returned by the given query.
+	 * </i></p>
+	 *
+	 * @param label			Name of the resulting table/CTE.
+	 * @param query			ADQL query returning the content of this CTE.
+	 * @param columnLabels	Labels of the output columns.
+	 */
+	public WithItem(final String label, final ADQLQuery query, final Collection<ADQLColumn> columnLabels) {
+		if (label == null || label.trim().isEmpty())
+			throw new NullPointerException("Missing label of the WITH item!");
+
+		if (query == null)
+			throw new NullPointerException("Missing query of the WITH item!");
+
+		setLabel(label);
+		this.query = query;
+		this.columnLabels = (columnLabels == null || columnLabels.isEmpty()) ? null : new ArrayList<>(columnLabels);
+
+	}
+
+	/**
+	 * Create a deep copy of the given WITH item.
+	 *
+	 * @param toCopy	The WITH item to duplicate.
+	 */
+	public WithItem(final WithItem toCopy) {
+		label = toCopy.label;
+		query = toCopy.query;
+		position = toCopy.position;
+		if (columnLabels != null) {
+			columnLabels = new ArrayList<>(toCopy.columnLabels.size());
+			for(ADQLColumn colLabel : toCopy.columnLabels)
+				columnLabels.add(colLabel);
+		}
+	}
+
+	@Override
+	public final String getName() {
+		return label;
+	}
+
+	@Override
+	public final LanguageFeature getFeatureDescription() {
+		return FEATURE;
+	}
+
+	/**
+	 * Get the name of the resulting table.
+	 *
+	 * @return	CTE's name.
+	 */
+	public final String getLabel() {
+		return label;
+	}
+
+	/**
+	 * Set the name of the resulting table.
+	 *
+	 * <p><i><b>Note:</b>
+	 * 	The given name may be delimited (i.e. surrounded by double quotes). If
+	 * 	so, it will be considered as case sensitive. Surrounding double quotes
+	 * 	will be removed and inner escaped double quotes will be un-escaped.
+	 * </i></p>
+	 *
+	 * @param label	New CTE's name.
+	 *
+	 * @throws NullPointerException	If the given name is NULL or empty.
+	 */
+	public final void setLabel(String label) throws NullPointerException {
+		String tmp = DBIdentifier.normalize(label);
+		if (tmp == null)
+			throw new NullPointerException("Missing CTE's label! (CTE = WITH's query)");
+		else {
+			this.label = tmp;
+			this.caseSensitive = DBIdentifier.isDelimited(label);
+		}
+	}
+
+	/**
+	 * Tell whether the resulting table name is case sensitive or not.
+	 *
+	 * @return	<code>true</code> if the CTE's name is case sensitive,
+	 *        	<code>false</code> otherwise.
+	 */
+	public final boolean isLabelCaseSensitive() {
+		return caseSensitive;
+	}
+
+	/**
+	 * Specify whether the resulting table name should be case sensitive or not.
+	 *
+	 * @param caseSensitive	<code>true</code> to make the CTE's name case
+	 *                     	sensitive,
+	 *                     	<code>false</code> otherwise.
+	 */
+	public final void setLabelCaseSensitive(final boolean caseSensitive) {
+		this.caseSensitive = caseSensitive;
+	}
+
+	/**
+	 * Get the specified labels of the output columns of this CTE.
+	 *
+	 * @return	CTE's columns labels,
+	 *        	or NULL if none is specified.
+	 */
+	public final List<ADQLColumn> getColumnLabels() {
+		return columnLabels;
+	}
+
+	/**
+	 * Specify the tables of all the output columns.
+	 *
+	 * @param newColumnLabels	New labels of the CTE's output columns,
+	 *                       	or NULL (or an empty list) to use the default
+	 *                       	column names instead.
+	 */
+	public final void setColumnLabels(final Collection<ADQLColumn> newColumnLabels) {
+		columnLabels = (newColumnLabels == null || newColumnLabels.isEmpty()) ? null : new ArrayList<>(newColumnLabels);
+	}
+
+	/**
+	 * Get the query corresponding to this CTE.
+	 *
+	 * @return	CTE's query.
+	 */
+	public final ADQLQuery getQuery() {
+		return query;
+	}
+
+	/**
+	 * Set the query returning the content of this CTE.
+	 *
+	 * @param query	New CTE's query.
+	 */
+	public final void setQuery(ADQLQuery query) {
+		this.query = query;
+	}
+
+	/**
+	 * Database description of this CTE.
+	 *
+	 * @return	CTE's metadata.
+	 */
+	public final DBTable getDBLink() {
+		return dbLink;
+	}
+
+	/**
+	 * Set the database description of this CTE.
+	 *
+	 * @param dbMeta	The new CTE's metadata.
+	 */
+	public final void setDBLink(final DBTable dbMeta) {
+		this.dbLink = dbMeta;
+	}
+
+	@Override
+	public final TextPosition getPosition() {
+		return position;
+	}
+
+	public final void setPosition(final TextPosition newPosition) {
+		position = newPosition;
+	}
+
+	@Override
+	public ADQLObject getCopy() throws Exception {
+		return new WithItem(this);
+	}
+
+	@Override
+	public ADQLIterator adqlIterator() {
+		return new ADQLIterator() {
+
+			private boolean queryReturned = false;
+
+			@Override
+			public ADQLObject next() {
+				if (queryReturned)
+					throw new NoSuchElementException("Iteration already finished! No more element available.");
+				else {
+					queryReturned = true;
+					return query;
+				}
+			}
+
+			@Override
+			public boolean hasNext() {
+				return !queryReturned;
+			}
+
+			@Override
+			public void replace(final ADQLObject replacer) throws UnsupportedOperationException, IllegalStateException {
+				if (!queryReturned)
+					throw new IllegalStateException("No iteration yet started!");
+				else if (replacer == null)
+					throw new UnsupportedOperationException("Impossible to remove the query from a WithItem object! You have to remove the WithItem from its ClauseWith for that.");
+				else if (!(replacer instanceof ADQLQuery))
+					throw new UnsupportedOperationException("Impossible to replace an ADQLQuery by a " + replacer.getClass() + "!");
+				else
+					query = (ADQLQuery)replacer;
+			}
+		};
+	}
+
+	@Override
+	public String toADQL() {
+		// Serialize the list of output columns:
+		StringBuffer bufOutColumns = new StringBuffer();
+		if (columnLabels != null && !columnLabels.isEmpty()) {
+			for(ADQLColumn col : columnLabels) {
+				bufOutColumns.append(bufOutColumns.length() == 0 ? '(' : ',');
+				bufOutColumns.append(DBIdentifier.denormalize(col.getColumnName(), col.isCaseSensitive(IdentifierField.COLUMN)));
+			}
+			bufOutColumns.append(')');
+		}
+		// And now serialize the whole WithItem:
+		return DBIdentifier.denormalize(label, caseSensitive) + bufOutColumns.toString() + " AS (\n" + query.toADQL() + "\n)";
+	}
+
+	/**
+	 * Get the description of all output columns.
+	 *
+	 * <p><i><b>Note 1:</b>
+	 * 	All resulting columns are returned, even if no column's label is
+	 * 	provided.
+	 * </i></p>
+	 *
+	 * <p><i><b>Note 2:</b>
+	 * 	List generated on the fly!
+	 * </i></p>
+	 *
+	 * @return	List and description of all output columns.
+	 */
+	public DBColumn[] getResultingColumns() {
+		// Fetch all resulting columns from the query:
+		DBColumn[] dbColumns = query.getResultingColumns();
+
+		// Force the writing of the column names:
+		boolean caseSensitive;
+		String newColLabel;
+		for(int i = 0; i < dbColumns.length; i++) {
+			// fetch the default column name and case sensitivity:
+			caseSensitive = dbColumns[i].isCaseSensitive();
+			newColLabel = dbColumns[i].getADQLName();
+
+			// if an explicit label is given, use it instead:
+			if (columnLabels != null && i < columnLabels.size()) {
+				caseSensitive = columnLabels.get(i).isCaseSensitive(IdentifierField.COLUMN);
+				newColLabel = columnLabels.get(i).getColumnName();
+			}
+
+			// reformat the column label in function of its case sensitivity:
+			if (caseSensitive)
+				newColLabel = DBIdentifier.denormalize(newColLabel, true);
+			else
+				newColLabel = newColLabel.toLowerCase();
+
+			// finally, copy the original column with this new name:
+			dbColumns[i] = dbColumns[i].copy(newColLabel, newColLabel, dbColumns[i].getTable());
+		}
+
+		return dbColumns;
+	}
+
+}
diff --git a/src/adql/query/operand/ADQLColumn.java b/src/adql/query/operand/ADQLColumn.java
index 1dd0ec5cae92e2ce7b16e82d54180a6192ed0236..b4aa7e51568347090005636961563859d48ff08f 100644
--- a/src/adql/query/operand/ADQLColumn.java
+++ b/src/adql/query/operand/ADQLColumn.java
@@ -34,7 +34,7 @@ import adql.query.from.ADQLTable;
  * ({schema(s)}.{table}.{column}).
  *
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 2.0 (07/2019)
+ * @version 2.0 (09/2019)
  */
 public class ADQLColumn implements ADQLOperand, UnknownType {
 
@@ -67,7 +67,9 @@ public class ADQLColumn implements ADQLOperand, UnknownType {
 
 	/** The {@link ADQLTable} which is supposed to contain this column.
 	 * By default, this field is automatically filled by
-	 * {@link adql.db.DBChecker}. */
+	 * {@link adql.db.DBChecker}.
+	 * @deprecated	Since v2.0. This piece of information is never used. */
+	@Deprecated
 	private ADQLTable adqlTable = null;
 
 	/** Type expected by the parser.
@@ -472,7 +474,10 @@ public class ADQLColumn implements ADQLOperand, UnknownType {
 	 * Gets the {@link ADQLTable} from which this column is supposed to come.
 	 *
 	 * @return 	Its source table.
+	 *
+	 * @deprecated	Since v2.0. This function is never used.
 	 */
+	@Deprecated
 	public final ADQLTable getAdqlTable() {
 		return adqlTable;
 	}
@@ -487,7 +492,10 @@ public class ADQLColumn implements ADQLOperand, UnknownType {
 	 * </p>
 	 *
 	 * @param adqlTable Its source table.
+	 *
+	 * @deprecated	Since v2.0. This piece of information is never used.
 	 */
+	@Deprecated
 	public final void setAdqlTable(ADQLTable adqlTable) {
 		this.adqlTable = adqlTable;
 	}
@@ -527,7 +535,10 @@ public class ADQLColumn implements ADQLOperand, UnknownType {
 
 	@Override
 	public String getName() {
-		return getColumnName();
+		if (dbLink != null)
+			return (dbLink.isCaseSensitive() ? dbLink.getADQLName() : dbLink.getADQLName().toLowerCase());
+		else
+			return getColumnName();
 	}
 
 	@Override
diff --git a/src/adql/translator/ADQLTranslator.java b/src/adql/translator/ADQLTranslator.java
index 9b0c651d2c40b07b6ae5c2b64f21d2b93587685e..d915931f69d59f50ebe0e3b5a3a5f3aa17b873f1 100644
--- a/src/adql/translator/ADQLTranslator.java
+++ b/src/adql/translator/ADQLTranslator.java
@@ -26,11 +26,13 @@ import adql.query.ADQLList;
 import adql.query.ADQLObject;
 import adql.query.ADQLOrder;
 import adql.query.ADQLQuery;
+import adql.query.ClauseADQL;
 import adql.query.ClauseConstraints;
 import adql.query.ClauseSelect;
 import adql.query.ColumnReference;
 import adql.query.SelectAllColumns;
 import adql.query.SelectItem;
+import adql.query.WithItem;
 import adql.query.constraint.ADQLConstraint;
 import adql.query.constraint.Between;
 import adql.query.constraint.Comparison;
@@ -51,9 +53,9 @@ 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.InUnitFunction;
 import adql.query.operand.function.UserDefinedFunction;
 import adql.query.operand.function.geometry.AreaFunction;
 import adql.query.operand.function.geometry.BoxFunction;
@@ -104,10 +106,16 @@ public interface ADQLTranslator {
 	/* ***** LIST & CLAUSE ***** */
 	public String translate(ADQLList<? extends ADQLObject> list) throws TranslationException;
 
+	/** @since 2.0 */
+	public String translate(ClauseADQL<WithItem> clause) throws TranslationException;
+
 	public String translate(ClauseSelect clause) throws TranslationException;
 
 	public String translate(ClauseConstraints clause) throws TranslationException;
 
+	/** @since 2.0 */
+	public String translate(WithItem item) throws TranslationException;
+
 	public String translate(SelectItem item) throws TranslationException;
 
 	public String translate(SelectAllColumns item) throws TranslationException;
diff --git a/src/adql/translator/JDBCTranslator.java b/src/adql/translator/JDBCTranslator.java
index bc482437f23a4b11a5a176f32a2a73fedf73eaca..f2da12c19cde507a298df47c0bea2496e5bf0b80 100644
--- a/src/adql/translator/JDBCTranslator.java
+++ b/src/adql/translator/JDBCTranslator.java
@@ -21,8 +21,10 @@ package adql.translator;
  */
 
 import java.util.Iterator;
+import java.util.List;
 
 import adql.db.DBColumn;
+import adql.db.DBIdentifier;
 import adql.db.DBTable;
 import adql.db.DBTableAlias;
 import adql.db.DBType;
@@ -33,12 +35,14 @@ import adql.query.ADQLList;
 import adql.query.ADQLObject;
 import adql.query.ADQLOrder;
 import adql.query.ADQLQuery;
+import adql.query.ClauseADQL;
 import adql.query.ClauseConstraints;
 import adql.query.ClauseSelect;
 import adql.query.ColumnReference;
 import adql.query.IdentifierField;
 import adql.query.SelectAllColumns;
 import adql.query.SelectItem;
+import adql.query.WithItem;
 import adql.query.constraint.ADQLConstraint;
 import adql.query.constraint.Between;
 import adql.query.constraint.Comparison;
@@ -329,8 +333,8 @@ public abstract class JDBCTranslator implements ADQLTranslator {
 	 * @return					The string buffer + identifier.
 	 */
 	public StringBuffer appendIdentifier(final StringBuffer str, final String id, final boolean caseSensitive) {
-		if (caseSensitive && !id.matches("\"[^\"]*\""))
-			return str.append('"').append(id).append('"');
+		if (caseSensitive && !DBIdentifier.isDelimited(id))
+			return str.append(DBIdentifier.denormalize(id, true));
 		else
 			return str.append(id);
 	}
@@ -354,13 +358,20 @@ public abstract class JDBCTranslator implements ADQLTranslator {
 			return translate((ADQLOperand)obj);
 		else if (obj instanceof ADQLConstraint)
 			return translate((ADQLConstraint)obj);
+		else if (obj instanceof WithItem)
+			return translate((WithItem)obj);
 		else
 			return obj.toADQL();
 	}
 
 	@Override
 	public String translate(ADQLQuery query) throws TranslationException {
-		StringBuffer sql = new StringBuffer(translate(query.getSelect()));
+		StringBuffer sql = new StringBuffer();
+
+		if (!query.getWith().isEmpty())
+			sql.append(translate(query.getWith())).append('\n');
+
+		sql.append(translate(query.getSelect()));
 
 		sql.append("\nFROM ").append(translate(query.getFrom()));
 
@@ -444,6 +455,11 @@ public abstract class JDBCTranslator implements ADQLTranslator {
 		return sql;
 	}
 
+	@Override
+	public String translate(final ClauseADQL<WithItem> clause) throws TranslationException {
+		return getDefaultADQLList(clause);
+	}
+
 	@Override
 	public String translate(ClauseSelect clause) throws TranslationException {
 		String sql = null;
@@ -468,6 +484,44 @@ public abstract class JDBCTranslator implements ADQLTranslator {
 			return getDefaultADQLList(clause);
 	}
 
+	@Override
+	public String translate(final WithItem item) throws TranslationException {
+		StringBuffer translation = new StringBuffer();
+
+		// query name/label:
+		if (item.getDBLink() != null)
+			appendIdentifier(translation, (item.getDBLink().isCaseSensitive() ? item.getDBLink().getDBName() : item.getDBLink().getDBName().toLowerCase()), true);
+		else
+			appendIdentifier(translation, (item.isLabelCaseSensitive() ? item.getLabel() : item.getLabel().toLowerCase()), true);
+
+		// output column labels (if any):
+		if (item.getDBLink() != null) {
+			boolean firstDone = false;
+			for(DBColumn dbCol : item.getDBLink()) {
+				translation.append(firstDone ? ',' : '(');
+				appendIdentifier(translation, dbCol.getADQLName(), true);
+				firstDone = true;
+			}
+			translation.append(')');
+		} else {
+			List<ADQLColumn> colLabels = item.getColumnLabels();
+			if (colLabels != null && !colLabels.isEmpty()) {
+				boolean firstDone = false;
+				for(ADQLColumn col : colLabels) {
+					translation.append(firstDone ? ',' : '(');
+					appendIdentifier(translation, (col.isCaseSensitive(IdentifierField.COLUMN) ? col.getColumnName() : col.getColumnName().toLowerCase()), true);
+					firstDone = true;
+				}
+				translation.append(')');
+			}
+		}
+
+		// query itself:
+		translation.append(" AS (\n").append(translate(item.getQuery())).append("\n)");
+
+		return translation.toString();
+	}
+
 	@Override
 	public String translate(SelectItem item) throws TranslationException {
 		if (item instanceof SelectAllColumns)
@@ -476,10 +530,7 @@ public abstract class JDBCTranslator implements ADQLTranslator {
 		StringBuffer translation = new StringBuffer(translate(item.getOperand()));
 		if (item.hasAlias()) {
 			translation.append(" AS ");
-			if (item.isCaseSensitive())
-				appendIdentifier(translation, item.getAlias(), true);
-			else
-				appendIdentifier(translation, item.getAlias().toLowerCase(), true);
+			appendIdentifier(translation, (item.isCaseSensitive() ? item.getAlias() : item.getAlias().toLowerCase()), true);
 		} else {
 			translation.append(" AS ");
 			appendIdentifier(translation, item.getName(), true);
@@ -508,7 +559,7 @@ public abstract class JDBCTranslator implements ADQLTranslator {
 			StringBuffer cols = new StringBuffer();
 			for(DBColumn col : dbCols) {
 				if (cols.length() > 0)
-					cols.append(',');
+					cols.append(" , ");
 				if (col.getTable() != null) {
 					if (col.getTable() instanceof DBTableAlias)
 						cols.append(getTableName(col.getTable(), false)).append('.');
@@ -516,7 +567,8 @@ public abstract class JDBCTranslator implements ADQLTranslator {
 						cols.append(getQualifiedTableName(col.getTable())).append('.');
 				}
 				appendIdentifier(cols, col.getDBName(), IdentifierField.COLUMN);
-				cols.append(" AS \"").append(col.getADQLName()).append('\"');
+				cols.append(" AS ");
+				appendIdentifier(cols, (col.isCaseSensitive() ? col.getADQLName() : col.getADQLName().toLowerCase()), true);
 			}
 			return (cols.length() > 0) ? cols.toString() : item.toADQL();
 		} else {
@@ -595,16 +647,19 @@ public abstract class JDBCTranslator implements ADQLTranslator {
 			/* In case where metadata are known, the alias must always be
 			 * written case sensitively in order to ensure a translation
 			 * stability (i.e. all references clearly point toward this alias
-			 * whatever is their character case). */
+			 * whatever is their character case). *
 			if (table.getDBLink() != null) {
-				if (table.isCaseSensitive(IdentifierField.ALIAS))
-					appendIdentifier(sql, table.getAlias(), true);
+				if (table.getDBLink().isCaseSensitive())
+					appendIdentifier(sql, table.getAlias(), IdentifierField.TABLE);
 				else
-					appendIdentifier(sql, table.getAlias().toLowerCase(), true);
+					appendIdentifier(sql, table.getAlias().toLowerCase(), IdentifierField.TABLE);
 			}
-			/* Otherwise, just write what is written in ADQL: */
+			/* Otherwise, just write what is written in ADQL: *
+			else*/
+			if (table.getDBLink() != null)
+				appendIdentifier(sql, (table.getDBLink().isCaseSensitive() ? table.getDBLink().getDBName() : table.getDBLink().getDBName().toLowerCase()), true);
 			else
-				appendIdentifier(sql, table.getAlias(), table.isCaseSensitive(IdentifierField.ALIAS));
+				appendIdentifier(sql, (table.isCaseSensitive(IdentifierField.ALIAS) ? table.getAlias() : table.getAlias().toLowerCase()), true);
 		}
 
 		return sql.toString();
@@ -676,14 +731,8 @@ public abstract class JDBCTranslator implements ADQLTranslator {
 			StringBuffer colName = new StringBuffer();
 
 			// Use the DBTable if any:
-			if (dbCol.getTable() != null && dbCol.getTable().getDBName() != null) {
-				/* Note: if the table is aliased, ensure no schema is prefixing
-				 *       this alias thanks to getTableName(..., false). */
-				if (dbCol.getTable() instanceof DBTableAlias)
-					colName.append(getTableName(dbCol.getTable(), false)).append('.');
-				else
-					colName.append(getQualifiedTableName(dbCol.getTable())).append('.');
-			}
+			if (dbCol.getTable() != null && dbCol.getTable().getDBName() != null)
+				colName.append(getQualifiedTableName(dbCol.getTable())).append('.');
 
 			// Otherwise, use the prefix of the column given in the ADQL query:
 			else if (column.getTableName() != null)
diff --git a/src/cds/utils/TextualSearchList.java b/src/cds/utils/TextualSearchList.java
index 2cd9c8df0eb13f7977101ef875077d83c75a6cbc..fbb05efdd20dc7ac7bbe28fc30d8a79466e8bfcf 100644
--- a/src/cds/utils/TextualSearchList.java
+++ b/src/cds/utils/TextualSearchList.java
@@ -2,20 +2,20 @@ package cds.utils;
 
 /*
  * This file is part of ADQLLibrary.
- * 
+ *
  * ADQLLibrary is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Lesser General Public License as published by
  * the Free Software Foundation, either version 3 of the License, or
  * (at your option) any later version.
- * 
+ *
  * ADQLLibrary is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU Lesser General Public License for more details.
- * 
+ *
  * You should have received a copy of the GNU Lesser General Public License
  * along with ADQLLibrary.  If not, see <http://www.gnu.org/licenses/>.
- * 
+ *
  * Copyright 2012-2017 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
  *                       Astronomisches Rechen Institut (ARI)
  */
@@ -37,108 +37,108 @@ import java.util.List;
  * 	by the {@link Object#toString() toString()} function will be used as key.
  * </p>
  * <p><b><u>WARNING:</u> The extracted key MUST be CASE-SENSITIVE and UNIQUE !</b></p>
- * 
+ *
  * @param <E>	Type of object to manage in this list.
  * @author 		Gr&eacute;gory Mantelet (CDS;ARI)
  * @version 	1.4 (09/2017)
  */
-public class TextualSearchList< E > extends ArrayList<E> {
+public class TextualSearchList<E> extends ArrayList<E> {
 	private static final long serialVersionUID = 1L;
 
 	/** Object to use to extract an unique textual string. */
 	public final KeyExtractor<E> keyExtractor;
 
 	/** Map which associates objects of type E with its textual string (case-sensitive). */
-	protected final HashMap<String,ArrayList<E>> csMap;
+	protected final HashMap<String, ArrayList<E>> csMap;
 
 	/** Map which associates objects of type E with their lower-case textual string. */
-	protected final HashMap<String,ArrayList<E>> ncsMap;
+	protected final HashMap<String, ArrayList<E>> ncsMap;
 
 	/* ************ */
 	/* CONSTRUCTORS */
 	/* ************ */
 	/**
 	 * <p>Builds an empty TextualSearchList.</p>
-	 * 
+	 *
 	 * <p><i><u>Note:</u>
 	 * 	the key of inserted objects will be the string returned by their {@link Object#toString() toString()} function.
 	 * </i></p>
-	 * 
+	 *
 	 * @see #TextualSearchList(KeyExtractor)
 	 */
-	public TextualSearchList(){
+	public TextualSearchList() {
 		this(new DefaultKeyExtractor<E>());
 	}
 
 	/**
 	 * Builds an empty TextualSearchList.
-	 * 
+	 *
 	 * @param keyExtractor	The object to use to extract a textual key from objects to insert.
-	 * 
+	 *
 	 * @see ArrayList#ArrayList()
 	 */
-	public TextualSearchList(final KeyExtractor<E> keyExtractor){
+	public TextualSearchList(final KeyExtractor<E> keyExtractor) {
 		super();
 		this.keyExtractor = keyExtractor;
-		csMap = new HashMap<String,ArrayList<E>>();
-		ncsMap = new HashMap<String,ArrayList<E>>();
+		csMap = new HashMap<String, ArrayList<E>>();
+		ncsMap = new HashMap<String, ArrayList<E>>();
 	}
 
 	/**
 	 * <p>Builds an empty TextualSearchList with an initial capacity.</p>
-	 * 
+	 *
 	 * <p><i><u>Note:</u>
 	 * 	the key of inserted objects will be the string returned by their {@link Object#toString() toString()} function.
 	 * </i></p>
-	 * 
+	 *
 	 * @param initialCapacity	Initial capacity of this list.
-	 * 
+	 *
 	 * @see #TextualSearchList(int, KeyExtractor)
 	 */
-	public TextualSearchList(int initialCapacity){
+	public TextualSearchList(int initialCapacity) {
 		this(initialCapacity, new DefaultKeyExtractor<E>());
 	}
 
 	/**
 	 * Builds an empty TextualSearchList with an initial capacity.
-	 * 
+	 *
 	 * @param initialCapacity	Initial capacity of this list.
 	 * @param keyExtractor		The object to use to extract a textual key from objects to insert.
-	 * 
+	 *
 	 * @see ArrayList#ArrayList(int)
 	 */
-	public TextualSearchList(final int initialCapacity, final KeyExtractor<E> keyExtractor){
+	public TextualSearchList(final int initialCapacity, final KeyExtractor<E> keyExtractor) {
 		super(initialCapacity);
 		this.keyExtractor = keyExtractor;
-		csMap = new HashMap<String,ArrayList<E>>(initialCapacity);
-		ncsMap = new HashMap<String,ArrayList<E>>(initialCapacity);
+		csMap = new HashMap<String, ArrayList<E>>(initialCapacity);
+		ncsMap = new HashMap<String, ArrayList<E>>(initialCapacity);
 	}
 
 	/**
 	 * <p>Builds a TextualSearchList filled with the objects of the given collection.</p>
-	 * 
+	 *
 	 * <p><i><u>Note:</u>
 	 * 	the key of inserted objects will be the string returned by their {@link Object#toString() toString()} function.
 	 * </i></p>
-	 * 
+	 *
 	 * @param c	Collection to copy into this list.
 	 */
-	public TextualSearchList(Collection<? extends E> c){
+	public TextualSearchList(Collection<? extends E> c) {
 		this(c, new DefaultKeyExtractor<E>());
 	}
 
 	/**
 	 * Builds a TextualSearchList filled with the objects of the given collection.
-	 * 
+	 *
 	 * @param c				Collection to copy into this list.
 	 * @param keyExtractor	The object object to use to extract a textual key from objects to insert.
-	 * 
+	 *
 	 * @see #addAll(Collection)
 	 */
-	public TextualSearchList(Collection<? extends E> c, final KeyExtractor<E> keyExtractor){
+	public TextualSearchList(Collection<? extends E> c, final KeyExtractor<E> keyExtractor) {
 		this.keyExtractor = keyExtractor;
-		csMap = new HashMap<String,ArrayList<E>>(c.size());
-		ncsMap = new HashMap<String,ArrayList<E>>(c.size());
+		csMap = new HashMap<String, ArrayList<E>>(c.size());
+		ncsMap = new HashMap<String, ArrayList<E>>(c.size());
 		addAll(c);
 	}
 
@@ -146,48 +146,48 @@ public class TextualSearchList< E > extends ArrayList<E> {
 	 * Returns true if this list contains the specified element.
 	 * More formally, returns true if and only if this list contains at least one element
 	 * e such that (keyExtractor.getKey(o).equals(keyExtractor.getKey(e))).
-	 * 
+	 *
 	 * @see java.util.ArrayList#contains(java.lang.Object)
 	 * @see #getKey(Object)
-	 * 
+	 *
 	 * @since 1.1
 	 */
 	@SuppressWarnings("unchecked")
 	@Override
-	public boolean contains(Object o){
-		try{
+	public boolean contains(Object o) {
+		try {
 			if (o == null)
 				return false;
-			else{
+			else {
 				E object = (E)o;
 				return !get(getKey(object)).isEmpty();
 			}
-		}catch(Exception e){
+		} catch(Exception e) {
 			return false;
 		}
 	}
 
 	/**
 	 * Searches (CASE-INSENSITIVE) the object which has the given key.
-	 * 
+	 *
 	 * @param key	Textual key of the object to search.
-	 * 
+	 *
 	 * @return		The corresponding object or <code>null</code>.
 	 */
-	public final List<E> get(final String key){
+	public final List<E> get(final String key) {
 		return get(key, false);
 	}
 
 	/**
 	 * Searches of all the object which has the given key.
-	 * 
+	 *
 	 * @param key			Textual key of the object to search.
 	 * @param caseSensitive	<i>true</i> to consider the case of the key, <i>false</i> otherwise.
-	 * 
+	 *
 	 * @return		All the objects whose the key is the same as the given one.
 	 */
 	@SuppressWarnings("unchecked")
-	public List<E> get(final String key, final boolean caseSensitive){
+	public List<E> get(final String key, final boolean caseSensitive) {
 		if (key == null)
 			return new ArrayList<E>(0);
 
@@ -200,15 +200,15 @@ public class TextualSearchList< E > extends ArrayList<E> {
 
 	/**
 	 * Generates and checks the key of the given object.
-	 * 
+	 *
 	 * @param value	The object whose the key must be generated.
-	 * 
+	 *
 	 * @return		Its corresponding key or <i>null</i> if this object already exist in this list.
-	 * 
+	 *
 	 * @throws NullPointerException		If the given object or its extracted key is <code>null</code>.
 	 * @throws IllegalArgumentException	If the extracted key is already used by another object in this list.
 	 */
-	private final String getKey(final E value) throws NullPointerException, IllegalArgumentException{
+	private final String getKey(final E value) throws NullPointerException, IllegalArgumentException {
 		String key = keyExtractor.getKey(value);
 		if (key == null)
 			throw new NullPointerException("Null keys are not allowed in a TextualSearchList !");
@@ -217,11 +217,11 @@ public class TextualSearchList< E > extends ArrayList<E> {
 
 	/**
 	 * Adds the given object in the two maps with the given key.
-	 * 
+	 *
 	 * @param key	The key with which the given object must be associated.
 	 * @param value	The object to add.
 	 */
-	private final void putIntoMaps(final String key, final E value){
+	private final void putIntoMaps(final String key, final E value) {
 		// update the case-sensitive map:
 		putIntoMap(csMap, key, value);
 		// update the case-INsensitive map:
@@ -230,35 +230,25 @@ public class TextualSearchList< E > extends ArrayList<E> {
 
 	/**
 	 * Adds the given object in the given map with the given key.
-	 * 
+	 *
 	 * @param map	The map in which the given value must be added.
 	 * @param key	The key with which the given object must be associated.
 	 * @param value	The object to add.
-	 * 
+	 *
 	 * @param <E>	The type of objects managed in the given map.
 	 */
-	private static final < E > void putIntoMap(final HashMap<String,ArrayList<E>> map, final String key, final E value){
+	private static final <E> void putIntoMap(final HashMap<String, ArrayList<E>> map, final String key, final E value) {
 		ArrayList<E> lst = map.get(key);
-		if (lst == null){
+		if (lst == null) {
 			lst = new ArrayList<E>();
 			lst.add(value);
 			map.put(key, lst);
-		}else
+		} else
 			lst.add(value);
 	}
 
-	/**
-	 * Adds the given object at the end of this list.
-	 * 
-	 * @param obj	Object to add (different from NULL).
-	 * 
-	 * @throws NullPointerException		If the given object or its extracted key is <code>null</code>.
-	 * @throws IllegalArgumentException	If the extracted key is already used by another object in this list.
-	 * 
-	 * @see java.util.ArrayList#add(java.lang.Object)
-	 */
 	@Override
-	public boolean add(E obj) throws NullPointerException, IllegalArgumentException{
+	public boolean add(E obj) throws NullPointerException, IllegalArgumentException {
 		if (obj == null)
 			throw new NullPointerException("Null objects are not allowed in a TextualSearchList !");
 
@@ -266,27 +256,27 @@ public class TextualSearchList< E > extends ArrayList<E> {
 		if (key == null)
 			return false;
 
-		if (super.add(obj)){
+		if (super.add(obj)) {
 			putIntoMaps(key, obj);
 			return true;
-		}else
+		} else
 			return false;
 	}
 
 	/**
 	 * Adds the given object at the given position in this list.
-	 * 
+	 *
 	 * @param index	Index at which the given object must be added.
 	 * @param obj	Object to add (different from NULL).
-	 * 
+	 *
 	 * @throws NullPointerException			If the given object or its extracted key is <code>null</code>.
 	 * @throws IllegalArgumentException		If the extracted key is already used by another object in this list.
 	 * @throws IndexOutOfBoundsException	If the given index is negative or greater than the size of this list.
-	 * 
+	 *
 	 * @see java.util.ArrayList#add(int, java.lang.Object)
 	 */
 	@Override
-	public void add(int index, E obj) throws NullPointerException, IllegalArgumentException, IndexOutOfBoundsException{
+	public void add(int index, E obj) throws NullPointerException, IllegalArgumentException, IndexOutOfBoundsException {
 		if (obj == null)
 			throw new NullPointerException("Null objects are not allowed in a TextualSearchList !");
 
@@ -301,19 +291,19 @@ public class TextualSearchList< E > extends ArrayList<E> {
 
 	/**
 	 * Appends all the objects of the given collection in this list.
-	 * 
+	 *
 	 * @param c	Collection of objects to add.
-	 * 
+	 *
 	 * @return <code>true</code> if this list changed as a result of the call, <code>false</code> otherwise.
-	 * 
+	 *
 	 * @throws NullPointerException		If an object to add or its extracted key is <code>null</code>.
 	 * @throws IllegalArgumentException	If the extracted key is already used by another object in this list.
-	 * 
+	 *
 	 * @see java.util.ArrayList#addAll(java.util.Collection)
 	 * @see #add(Object)
 	 */
 	@Override
-	public boolean addAll(Collection<? extends E> c) throws NullPointerException, IllegalArgumentException{
+	public boolean addAll(Collection<? extends E> c) throws NullPointerException, IllegalArgumentException {
 		if (c == null)
 			return false;
 
@@ -326,27 +316,27 @@ public class TextualSearchList< E > extends ArrayList<E> {
 
 	/**
 	 * Appends all the objects of the given collection in this list after the given position.
-	 * 
+	 *
 	 * @param index	Position from which objects of the given collection must be added.
 	 * @param c		Collection of objects to add.
-	 * 
+	 *
 	 * @return <code>true</code> if this list changed as a result of the call, <code>false</code> otherwise.
-	 * 
+	 *
 	 * @throws NullPointerException			If an object to add or its extracted key is <code>null</code>.
 	 * @throws IllegalArgumentException		If the extracted key is already used by another object in this list.
 	 * @throws IndexOutOfBoundsException	If the given index is negative or greater than the size of this list.
-	 * 
+	 *
 	 * @see java.util.ArrayList#addAll(int, java.util.Collection)
 	 * @see #add(int, Object)
 	 */
 	@Override
-	public boolean addAll(int index, Collection<? extends E> c) throws NullPointerException, IllegalArgumentException, IndexOutOfBoundsException{
+	public boolean addAll(int index, Collection<? extends E> c) throws NullPointerException, IllegalArgumentException, IndexOutOfBoundsException {
 		if (c == null)
 			return false;
 
 		boolean modified = false;
 		int ind = index;
-		for(E obj : c){
+		for(E obj : c) {
 			add(ind++, obj);
 			modified = get(ind).equals(obj);
 		}
@@ -356,20 +346,20 @@ public class TextualSearchList< E > extends ArrayList<E> {
 
 	/**
 	 * Replaces the element at the specified position in this list with the specified element.
-	 * 
+	 *
 	 * @param index	Position of the object to replace.
 	 * @param obj	Object to be stored at the given position (different from NULL).
-	 * 
+	 *
 	 * @return Replaced object.
-	 * 
+	 *
 	 * @throws NullPointerException			If the object to add or its extracted key is <code>null</code>.
 	 * @throws IllegalArgumentException		If the extracted key is already used by another object in this list.
 	 * @throws IndexOutOfBoundsException	If the given index is negative or greater than the size of this list.
-	 * 
+	 *
 	 * @see java.util.ArrayList#set(int, java.lang.Object)
 	 */
 	@Override
-	public E set(int index, E obj) throws NullPointerException, IllegalArgumentException{
+	public E set(int index, E obj) throws NullPointerException, IllegalArgumentException {
 		if (obj == null)
 			throw new NullPointerException("Null objects are not allowed in a TextualSearchList !");
 
@@ -391,7 +381,7 @@ public class TextualSearchList< E > extends ArrayList<E> {
 	}
 
 	@Override
-	public void clear(){
+	public void clear() {
 		super.clear();
 		csMap.clear();
 		ncsMap.clear();
@@ -399,11 +389,11 @@ public class TextualSearchList< E > extends ArrayList<E> {
 
 	/**
 	 * Removes the given object associated with the given key from the two maps.
-	 * 
+	 *
 	 * @param key	The key associated with the given object.
 	 * @param value	The object to remove.
 	 */
-	private final void removeFromMaps(final String key, final E value){
+	private final void removeFromMaps(final String key, final E value) {
 		// update the case-sensitive map:
 		removeFromMap(csMap, key, value);
 		// update the case-insensitive map:
@@ -412,16 +402,16 @@ public class TextualSearchList< E > extends ArrayList<E> {
 
 	/**
 	 * Removes the given object associated with the given key from the given map.
-	 * 
+	 *
 	 * @param map	The map from which the given object must be removed.
 	 * @param key	The key associated with the given object.
 	 * @param value	The object to remove.
-	 * 
+	 *
 	 * @param <E>	The type of objects managed in the given map.
 	 */
-	private static final < E > void removeFromMap(final HashMap<String,ArrayList<E>> map, final String key, final E value){
+	private static final <E> void removeFromMap(final HashMap<String, ArrayList<E>> map, final String key, final E value) {
 		List<E> lst = map.get(key);
-		if (lst != null){
+		if (lst != null) {
 			lst.remove(value);
 			if (lst.isEmpty())
 				map.remove(key);
@@ -429,9 +419,9 @@ public class TextualSearchList< E > extends ArrayList<E> {
 	}
 
 	@Override
-	public E remove(int index){
+	public E remove(int index) {
 		E removed = super.remove(index);
-		if (removed != null){
+		if (removed != null) {
 			String key = keyExtractor.getKey(removed);
 			removeFromMaps(key, removed);
 		}
@@ -440,9 +430,9 @@ public class TextualSearchList< E > extends ArrayList<E> {
 
 	@SuppressWarnings("unchecked")
 	@Override
-	public boolean remove(Object obj){
+	public boolean remove(Object obj) {
 		boolean removed = super.remove(obj);
-		if (removed){
+		if (removed) {
 			String key = keyExtractor.getKey((E)obj);
 			removeFromMaps(key, (E)obj);
 		}
@@ -450,7 +440,7 @@ public class TextualSearchList< E > extends ArrayList<E> {
 	}
 
 	@Override
-	protected void removeRange(int fromIndex, int toIndex) throws IndexOutOfBoundsException{
+	protected void removeRange(int fromIndex, int toIndex) throws IndexOutOfBoundsException {
 		if (fromIndex < 0 || fromIndex >= size() || toIndex < 0 || toIndex >= size() || fromIndex > toIndex)
 			throw new IndexOutOfBoundsException("Incorrect range indexes: from " + fromIndex + " to " + toIndex + " !");
 
@@ -463,12 +453,12 @@ public class TextualSearchList< E > extends ArrayList<E> {
 	/* ************************************************ */
 	/**
 	 * Lets extract an unique textual key (case-sensitive) from a given type of object.
-	 * 
+	 *
 	 * @author G&eacute;gory Mantelet (CDS)
 	 * @param <E>	Type of object from which a textual key must be extracted.
 	 * @see TextualSearchList
 	 */
-	public static interface KeyExtractor< E > {
+	public static interface KeyExtractor<E> {
 		/**
 		 * Extracts an UNIQUE textual key (case-sensitive) from the given object.
 		 * @param obj	Object from which a textual key must be extracted.
@@ -480,13 +470,13 @@ public class TextualSearchList< E > extends ArrayList<E> {
 	/**
 	 * Default implementation of {@link KeyExtractor}.
 	 * The extracted key is the string returned by the {@link Object#toString() toString()} function.
-	 * 
+	 *
 	 * @author Gr&eacute;gory Mantelet (CDS)
 	 * @param <E>	Type of object from which a textual key must be extracted.
 	 */
-	protected static class DefaultKeyExtractor< E > implements KeyExtractor<E> {
+	protected static class DefaultKeyExtractor<E> implements KeyExtractor<E> {
 		@Override
-		public String getKey(final E obj){
+		public String getKey(final E obj) {
 			return obj.toString();
 		}
 	}
diff --git a/src/tap/db/JDBCConnection.java b/src/tap/db/JDBCConnection.java
index 6f2ac2fe38acd0edeadcfbdf5de597f7f86dbafd..1aaff3bdfcd76283466cd09802f36530a4a4fd81 100644
--- a/src/tap/db/JDBCConnection.java
+++ b/src/tap/db/JDBCConnection.java
@@ -39,6 +39,7 @@ import java.util.Map;
 import java.util.Properties;
 
 import adql.db.DBColumn;
+import adql.db.DBIdentifier;
 import adql.db.DBType;
 import adql.db.DBType.DBDatatype;
 import adql.db.STCS;
@@ -1081,7 +1082,7 @@ public class JDBCConnection implements DBConnection {
 
 				// create the new schema:
 				TAPSchema newSchema = new TAPSchema(schemaName, nullifyIfNeeded(description), nullifyIfNeeded(utype));
-				if (dbName != null && dbName.trim().length() > 0)
+				if (DBIdentifier.normalize(dbName) != null)
 					newSchema.setDBName(dbName);
 				newSchema.setIndex(schemaIndex);
 
diff --git a/src/tap/metadata/TAPColumn.java b/src/tap/metadata/TAPColumn.java
index 6516976426b494eb8936357b95b132c9acfac054..ef4689ff369c34aeb430ca24d8fc6738e574b444 100644
--- a/src/tap/metadata/TAPColumn.java
+++ b/src/tap/metadata/TAPColumn.java
@@ -26,10 +26,10 @@ import java.util.Iterator;
 import java.util.Map;
 
 import adql.db.DBColumn;
+import adql.db.DBIdentifier;
 import adql.db.DBTable;
 import adql.db.DBType;
 import adql.db.DBType.DBDatatype;
-import adql.db.DefaultDBTable;
 
 /**
  * Represent a column as described by the IVOA standard in the TAP protocol
@@ -82,21 +82,7 @@ import adql.db.DefaultDBTable;
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
  * @version 2.4 (09/2019)
  */
-public class TAPColumn implements DBColumn {
-
-	/** ADQL name of this column. */
-	private final String adqlName;
-
-	/** Indicate whether the ADQL column name is case sensitive. In such case,
-	 * this name will be put between double quotes in ADQL.
-	 * @since 2.4 */
-	private boolean columnCaseSensitive = false;
-
-	/** Name that this column have in the database.
-	 * <p><i><b>Note:</b>
-	 * 	It CAN NOT be NULL. By default, it is the ADQL name.
-	 * </i></p> */
-	private String dbName = null;
+public class TAPColumn extends DBIdentifier implements DBColumn {
 
 	/** Table which owns this column.
 	 * <p><i><b>Note:</b>
@@ -228,20 +214,10 @@ public class TAPColumn implements DBColumn {
 	 *
 	 * @param columnName	ADQL name of this column.
 	 *
-	 * @throws NullPointerException	If the given name is NULL or an empty string.
+	 * @throws NullPointerException	If the given name is NULL or empty.
 	 */
-	public TAPColumn(String columnName) throws NullPointerException {
-		if (columnName == null)
-			throw new NullPointerException("Missing column name!");
-
-		columnName = columnName.trim();
-		columnCaseSensitive = DefaultDBTable.isDelimited(columnName);
-		adqlName = (columnCaseSensitive ? columnName.substring(1, columnName.length() - 1).replaceAll("\"\"", "\"") : columnName);
-
-		if (adqlName.trim().length() == 0)
-			throw new NullPointerException("Missing column name!");
-
-		dbName = null;
+	public TAPColumn(final String columnName) throws NullPointerException {
+		super(columnName);
 
 		lstTargets = new ArrayList<TAPForeignKey>(1);
 		lstSources = new ArrayList<TAPForeignKey>(1);
@@ -277,7 +253,7 @@ public class TAPColumn implements DBColumn {
 	 * @param columnName	ADQL name of this column.
 	 * @param type			Datatype of this column.
 	 *
-	 * @throws NullPointerException	If the given name is NULL or an empty string.
+	 * @throws NullPointerException	If the given name is NULL or empty.
 	 *
 	 * @see #setDatatype(DBType)
 	 */
@@ -318,7 +294,7 @@ public class TAPColumn implements DBColumn {
 	 * @param description	Description of the column's content.
 	 *                   	<i>May be NULL</i>
 	 *
-	 * @throws NullPointerException	If the given name is NULL or an empty string.
+	 * @throws NullPointerException	If the given name is NULL or empty.
 	 */
 	public TAPColumn(String columnName, String description) throws NullPointerException {
 		this(columnName, (DBType)null, description);
@@ -357,7 +333,7 @@ public class TAPColumn implements DBColumn {
 	 * @param description	Description of the column's content.
 	 *                   	<i>May be NULL</i>
 	 *
-	 * @throws NullPointerException	If the given name is NULL or an empty string.
+	 * @throws NullPointerException	If the given name is NULL or empty.
 	 */
 	public TAPColumn(String columnName, DBType type, String description) throws NullPointerException {
 		this(columnName, type);
@@ -398,7 +374,7 @@ public class TAPColumn implements DBColumn {
 	 * @param unit			Unit of the column's values.
 	 *            			<i>May be NULL</i>
 	 *
-	 * @throws NullPointerException	If the given name is NULL or an empty string.
+	 * @throws NullPointerException	If the given name is NULL or empty.
 	 */
 	public TAPColumn(String columnName, String description, String unit) throws NullPointerException {
 		this(columnName, null, description, unit);
@@ -438,7 +414,7 @@ public class TAPColumn implements DBColumn {
 	 * @param unit			Unit of the column's values.
 	 *            			<i>May be NULL</i>
 	 *
-	 * @throws NullPointerException	If the given name is NULL or an empty string.
+	 * @throws NullPointerException	If the given name is NULL or empty.
 	 */
 	public TAPColumn(String columnName, DBType type, String description, String unit) throws NullPointerException {
 		this(columnName, type, description);
@@ -482,7 +458,7 @@ public class TAPColumn implements DBColumn {
 	 * @param utype			UType associating this column with a data-model.
 	 *            			<i>May be NULL</i>
 	 *
-	 * @throws NullPointerException	If the given name is NULL or an empty string.
+	 * @throws NullPointerException	If the given name is NULL or empty.
 	 */
 	public TAPColumn(String columnName, String description, String unit, String ucd, String utype) throws NullPointerException {
 		this(columnName, null, description, unit, ucd, utype);
@@ -526,7 +502,7 @@ public class TAPColumn implements DBColumn {
 	 * @param utype			UType associating this column with a data-model.
 	 *                   	<i>May be NULL</i>
 	 *
-	 * @throws NullPointerException	If the given name is NULL or an empty string.
+	 * @throws NullPointerException	If the given name is NULL or empty.
 	 */
 	public TAPColumn(String columnName, DBType type, String description, String unit, String ucd, String utype) throws NullPointerException {
 		this(columnName, type, description, unit);
@@ -546,11 +522,6 @@ public class TAPColumn implements DBColumn {
 		return getADQLName();
 	}
 
-	@Override
-	public final String getADQLName() {
-		return adqlName;
-	}
-
 	/**
 	 * Get the ADQL name of this column, as it has been provided at
 	 * initialization.
@@ -560,36 +531,7 @@ public class TAPColumn implements DBColumn {
 	 * @since 2.1
 	 */
 	public final String getRawName() {
-		return (columnCaseSensitive ? "\"" + adqlName.replaceAll("\"", "\"\"") + "\"" : adqlName);
-	}
-
-	@Override
-	public final boolean isCaseSensitive() {
-		return columnCaseSensitive;
-	}
-
-	@Override
-	public final String getDBName() {
-		return (dbName == null) ? getADQLName() : dbName;
-	}
-
-	/**
-	 * Change the name that this column MUST have in the database (i.e. in SQL
-	 * queries).
-	 *
-	 * <p><i><b>Note:</b>
-	 * 	If the given value is NULL or an empty string, nothing is done ; the DB
-	 * 	name keeps is former value.
-	 * </i></p>
-	 *
-	 * @param name	The new database name of this column.
-	 */
-	public final void setDBName(String name) {
-		name = (name != null) ? name.trim() : name;
-		if (name != null && name.length() > 0)
-			dbName = name;
-		else
-			dbName = null;
+		return denormalize(getADQLName(), isCaseSensitive());
 	}
 
 	@Override
@@ -1033,8 +975,7 @@ public class TAPColumn implements DBColumn {
 	 */
 	@Override
 	public DBColumn copy(final String dbName, final String adqlName, final DBTable dbTable) {
-		TAPColumn copy = new TAPColumn((adqlName == null) ? this.adqlName : adqlName, datatype, description, unit, ucd, utype);
-		copy.columnCaseSensitive = this.columnCaseSensitive;
+		TAPColumn copy = new TAPColumn((adqlName == null) ? getRawName() : adqlName, datatype, description, unit, ucd, utype);
 		copy.setDBName((dbName == null) ? this.getDBName() : dbName);
 		copy.setTable(dbTable);
 
@@ -1060,7 +1001,7 @@ public class TAPColumn implements DBColumn {
 	 */
 	public DBColumn copy() {
 		TAPColumn copy = new TAPColumn(adqlName, datatype, description, unit, ucd, utype);
-		copy.columnCaseSensitive = this.columnCaseSensitive;
+		copy.setCaseSensitive(isCaseSensitive());
 		copy.setDBName(dbName);
 		copy.setTable(table);
 		copy.setIndexed(indexed);
@@ -1076,12 +1017,12 @@ public class TAPColumn implements DBColumn {
 			return false;
 
 		TAPColumn col = (TAPColumn)obj;
-		return col.getTable().equals(table) && col.getADQLName().equals(adqlName) && col.columnCaseSensitive == this.columnCaseSensitive;
+		return col.getTable().equals(table) && col.getADQLName().equals(adqlName) && col.isCaseSensitive() == this.isCaseSensitive();
 	}
 
 	@Override
 	public String toString() {
-		return (table != null ? table.toString() : "") + getRawName();
+		return (table != null ? table.toString() + "." : "") + getRawName();
 	}
 
 }
diff --git a/src/tap/metadata/TAPSchema.java b/src/tap/metadata/TAPSchema.java
index f1e6e0c3c33f721f6096be44d6ea392f511f4430..0fe26e827daa5dbbcbb453f03c826c6cbd216b68 100644
--- a/src/tap/metadata/TAPSchema.java
+++ b/src/tap/metadata/TAPSchema.java
@@ -25,7 +25,7 @@ import java.util.Iterator;
 import java.util.LinkedHashMap;
 import java.util.Map;
 
-import adql.db.DefaultDBTable;
+import adql.db.DBIdentifier;
 import tap.metadata.TAPTable.TableType;
 
 /**
@@ -53,23 +53,7 @@ import tap.metadata.TAPTable.TableType;
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
  * @version 2.4 (09/2019)
  */
-public class TAPSchema implements Iterable<TAPTable> {
-
-	/** Name that this schema MUST have in ADQL queries. */
-	private final String adqlName;
-
-	/** Indicate whether the ADQL schema name must be considered as case
-	 * sensitive. In such case, it should be provided between double quotes in
-	 * the constructor parameter.
-	 * @since 2.4 */
-	private boolean schemaCaseSensitive;
-
-	/** Name that this schema have in the database.
-	 * <p><i><b>Note:</b>
-	 * 	NULL by default. When NULL, {@link #getDBName()} returns exactly what
-	 * 	{@link #getADQLName()} returns.
-	 * </i></p> */
-	private String dbName = null;
+public class TAPSchema extends DBIdentifier implements Iterable<TAPTable> {
 
 	/** Descriptive, human-interpretable name of the schema.
 	 * <p><i><b>Note:</b>
@@ -135,18 +119,10 @@ public class TAPSchema implements Iterable<TAPTable> {
 	 *
 	 * @param schemaName	ADQL name of this schema.
 	 *
-	 * @throws NullPointerException	If the given name is NULL or an empty string.
+	 * @throws NullPointerException	If the given name is NULL or empty.
 	 */
 	public TAPSchema(String schemaName) throws NullPointerException {
-		if (schemaName == null)
-			throw new NullPointerException("Missing schema name!");
-
-		schemaName = schemaName.trim();
-		schemaCaseSensitive = DefaultDBTable.isDelimited(schemaName);
-		adqlName = (schemaCaseSensitive ? schemaName.substring(1, schemaName.length() - 1).replaceAll("\"\"", "\"") : schemaName);
-
-		if (getADQLName().trim().length() == 0)
-			throw new NullPointerException("Missing schema name!");
+		super(schemaName);
 
 		dbName = getADQLName();
 
@@ -236,15 +212,6 @@ public class TAPSchema implements Iterable<TAPTable> {
 		return getADQLName();
 	}
 
-	/**
-	 * Get the (non delimited) ADQL name of this schema.
-	 *
-	 * @return	Its ADQL name.
-	 */
-	public final String getADQLName() {
-		return adqlName;
-	}
-
 	/**
 	 * Get the full ADQL name of this schema, as it has been provided at
 	 * initialization (i.e. delimited if {@link #isCaseSensitive() case sensitive}).
@@ -257,33 +224,12 @@ public class TAPSchema implements Iterable<TAPTable> {
 		return toString();
 	}
 
-	/**
-	 * Tell whether the ADQL name of this schema should be considered as case
-	 * sensitive or not.
-	 *
-	 * @return	<code>true</code> if the ADQL name is case sensitive,
-	 *        	<code>false</code> otherwise.
-	 */
-	public final boolean isCaseSensitive() {
-		return schemaCaseSensitive;
-	}
-
-	/**
-	 * Let specify whether the ADQL name of this schema should be considered as
-	 * case sensitive or not.
-	 *
-	 * @param sensitive	<code>true</code> to make the ADQL name case sensitive,
-	 *                 	<code>false</code> otherwise.
-	 */
-	public final void setCaseSensitive(final boolean sensitive) {
-		schemaCaseSensitive = sensitive;
-	}
-
 	/**
 	 * Get the name this schema MUST have in the database.
 	 *
 	 * @return	Its DB name. <i>MAY be NULL</i>
 	 */
+	@Override
 	public final String getDBName() {
 		return dbName;
 	}
@@ -308,6 +254,7 @@ public class TAPSchema implements Iterable<TAPTable> {
 	 *
 	 * @param name	Its new DB name. <i>MAY be NULL</i>
 	 */
+	@Override
 	public final void setDBName(String name) {
 		name = (name != null) ? name.trim() : name;
 		dbName = name;
@@ -613,7 +560,7 @@ public class TAPSchema implements Iterable<TAPTable> {
 
 	@Override
 	public String toString() {
-		return (schemaCaseSensitive ? "\"" + adqlName.replaceAll("\"", "\"\"") + "\"" : adqlName);
+		return denormalize(getADQLName(), isCaseSensitive());
 	}
 
 }
diff --git a/src/tap/metadata/TAPTable.java b/src/tap/metadata/TAPTable.java
index 312905ae4da8a0699b6c05e1b95d3a91ee87f6bc..98e03d2b8c25b86314ab85a40144bcd6a50e35d2 100644
--- a/src/tap/metadata/TAPTable.java
+++ b/src/tap/metadata/TAPTable.java
@@ -28,9 +28,9 @@ import java.util.LinkedHashMap;
 import java.util.Map;
 
 import adql.db.DBColumn;
+import adql.db.DBIdentifier;
 import adql.db.DBTable;
 import adql.db.DBType;
-import adql.db.DefaultDBTable;
 import tap.TAPException;
 
 /**
@@ -63,7 +63,7 @@ import tap.TAPException;
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
  * @version 2.4 (09/2019)
  */
-public class TAPTable implements DBTable {
+public class TAPTable extends DBIdentifier implements DBTable {
 
 	/**
 	 * Different types of table according to the TAP protocol.
@@ -78,26 +78,10 @@ public class TAPTable implements DBTable {
 		output, table, view;
 	}
 
-	/** ADQL name of this table.
-	 * <p><i>This name is neither qualified nor delimited.</i></p> */
-	private String adqlName;
-
 	/** Name of this table as provided at creation.
 	 * <p><i>This name may be qualified and/or delimited.</i></p>
 	 * @since 2.4 */
-	private final String rawName;
-
-	/** Indicate whether the ADQL table name is case sensitive. In such case,
-	 * this name will be put between double quotes in ADQL.
-	 * @since 2.4 */
-	private boolean tableNameCaseSensitive = false;
-
-	/** Name that this table have in the database.
-	 * <p><i><b>Note:</b>
-	 * 	If NULL, {@link #getDBName()} returns what {@link #getADQLName()}
-	 * 	returns.
-	 * </i></p> */
-	private String dbName = null;
+	private String rawName;
 
 	/** The schema which owns this table.
 	 * <p><i><b>Note:</b>
@@ -181,7 +165,7 @@ public class TAPTable implements DBTable {
 	 * 		Double quotes may surround the table name. In such case, the ADQL
 	 * 		name of this table will be considered as case sensitive and these
 	 * 		double quotes will be automatically removed.
-	 * 		<em>Note that this case sensitivity may not be identified just after
+	 * 		<em>Note that this case sensitivity may be not identified just after
 	 * 		this constructor ; you may have to specify the schema
 	 * 		(see {@link #setSchema(TAPSchema)}) so that the schema prefix is
 	 * 		removed first.</em>
@@ -190,19 +174,12 @@ public class TAPTable implements DBTable {
 	 *
 	 * @param tableName		ADQL name of this table.
 	 *
-	 * @throws NullPointerException	If the given name is NULL or an empty string.
+	 * @throws NullPointerException	If the given name is NULL or empty.
 	 */
 	public TAPTable(final String tableName) throws NullPointerException {
-		if (tableName == null)
-			throw new NullPointerException("Missing table name!");
+		super(tableName);
 
 		rawName = tableName.trim();
-		updateADQLName();
-
-		if (adqlName.trim().length() == 0)
-			throw new NullPointerException("Missing table name!");
-
-		dbName = null;
 
 		columns = new LinkedHashMap<String, TAPColumn>();
 		foreignKeys = new ArrayList<TAPForeignKey>();
@@ -312,7 +289,7 @@ public class TAPTable implements DBTable {
 	 * @return	Qualified and delimited (if needed) ADQL name of this table.
 	 */
 	public final String getFullName() {
-		return (schema != null ? schema.getADQLName() + "." : "") + (tableNameCaseSensitive ? "\"" + getADQLName().replaceAll("\"", "\"\"") + "\"" : getADQLName());
+		return (schema != null ? schema.getADQLName() + "." : "") + denormalize(getADQLName(), isCaseSensitive());
 	}
 
 	/**
@@ -329,11 +306,6 @@ public class TAPTable implements DBTable {
 		return getADQLName();
 	}
 
-	@Override
-	public final String getADQLName() {
-		return adqlName;
-	}
-
 	/**
 	 * Get the full ADQL name of this table, as it has been provided at
 	 * initialization.
@@ -358,62 +330,38 @@ public class TAPTable implements DBTable {
 	 *
 	 * @since 2.4
 	 */
-	private void updateADQLName() {
-		String tmp = rawName;
+	@Override
+	public void setADQLName(final String name) throws NullPointerException {
+		/* Start by setting the new ADQL name (ignoring prefix if any
+		 * + detection of NULL and empty string): */
+		super.setADQLName(name);
+
+		// Memorize the new raw name:
+		rawName = name.trim();
 
 		// If a schema is specified, remove the schema prefix (if any):
 		if (schema != null) {
+			String tmp = name;
+
 			// strict comparison if schema is case sensitive:
 			if (schema.isCaseSensitive()) {
 				if (tmp.startsWith(schema.getRawName() + "."))
 					tmp = tmp.substring(schema.getRawName().length() + 1).trim();
 			}
+
 			// if no case sensitivity...
 			else {
 				// ...search not-case-sensitively for a prefix:
 				if (tmp.toLowerCase().startsWith(schema.getADQLName().toLowerCase() + "."))
 					tmp = tmp.substring(schema.getADQLName().length() + 1).trim();
 				// ...otherwise, try with a strict comparison (as if schema was case sensitive):
-				else if (tmp.toLowerCase().startsWith("\"" + schema.getADQLName().toLowerCase().replaceAll("\"", "\"\"") + "\"."))
-					tmp = tmp.substring(schema.getADQLName().replaceAll("\"", "\"\"").length() + 3).trim();
+				else if (tmp.toLowerCase().startsWith(denormalize(schema.getADQLName().toLowerCase(), true) + "."))
+					tmp = tmp.substring(denormalize(schema.getADQLName(), true).length() + 1).trim();
 			}
-		}
 
-		// Detect if delimited (i.e. case sensitive):
-		if ((tableNameCaseSensitive = DefaultDBTable.isDelimited(tmp)))
-			tmp = tmp.substring(1, tmp.length() - 1).replaceAll("\"\"", "\"");
-
-		// Finally, set the ADQL name:
-		adqlName = tmp;
-	}
-
-	@Override
-	public final boolean isCaseSensitive() {
-		return tableNameCaseSensitive;
-	}
-
-	@Override
-	public final String getDBName() {
-		return (dbName == null) ? getADQLName() : dbName;
-	}
-
-	/**
-	 * Change the name that this table MUST have in the database (i.e. in SQL
-	 * queries).
-	 *
-	 * <p><i><b>Note:</b>
-	 * 	If the given value is NULL or an empty string, {@link #getDBName()} will
-	 * 	return exactly what {@link #getADQLName()} returns.
-	 * </i></p>
-	 *
-	 * @param name	The new database name of this table.
-	 */
-	public final void setDBName(String name) {
-		name = (name != null) ? name.trim() : name;
-		if (name != null && name.length() > 0)
-			dbName = name;
-		else
-			dbName = null;
+			// Finally, re-update the ADQL name (with prefix removed):
+			super.setADQLName(tmp);
+		}
 	}
 
 	@Override
@@ -471,7 +419,7 @@ public class TAPTable implements DBTable {
 
 		/* Update the ADQL name of this table:
 		 * (i.e. whether or not schema prefix should be removed) */
-		updateADQLName();
+		setADQLName(rawName);
 	}
 
 	/**
@@ -1131,7 +1079,7 @@ public class TAPTable implements DBTable {
 
 	@Override
 	public String toString() {
-		return ((schema != null) ? (schema.toString() + ".") : "") + (tableNameCaseSensitive ? "\"" + adqlName.replaceAll("\"", "\"\"") + "\"" : getADQLName());
+		return ((schema != null) ? (schema.toString() + ".") : "") + denormalize(getADQLName(), isCaseSensitive());
 	}
 
 	@Override
diff --git a/test/adql/db/TestDBChecker.java b/test/adql/db/TestDBChecker.java
index 391d2320da034bf4a5cbc9f76784b192b3893e1c..7d5288f7ea214fb26a6bc5c91745ff0e19e81288 100644
--- a/test/adql/db/TestDBChecker.java
+++ b/test/adql/db/TestDBChecker.java
@@ -46,7 +46,7 @@ public class TestDBChecker {
 		tables = new ArrayList<DBTable>();
 
 		DefaultDBTable fooTable = new DefaultDBTable(null, "aschema", "foo");
-		DBColumn col = new DefaultDBColumn("colS", new DBType(DBDatatype.VARCHAR), fooTable);
+		DBColumn col = new DefaultDBColumn("\"colS\"", new DBType(DBDatatype.VARCHAR), fooTable);
 		fooTable.addColumn(col);
 		col = new DefaultDBColumn("colI", new DBType(DBDatatype.INTEGER), fooTable);
 		fooTable.addColumn(col);
@@ -74,6 +74,60 @@ public class TestDBChecker {
 	public void tearDown() throws Exception {
 	}
 
+	@Test
+	public void testWithClause() {
+
+		ADQLParser parser = new ADQLParser(ADQLVersion.V2_1);
+		parser.setQueryChecker(new DBChecker(tables));
+
+		try {
+
+			// CASE: Only 1 CTE, no column label, no CTE case sensibility
+			assertNotNull(parser.parseQuery("WITH myfoo AS (SELECT * FROM foo) SELECT * FROM myfoo"));
+
+			// CASE: CTE case sensibility respected
+			assertNotNull(parser.parseQuery("WITH \"MyFoo\" AS (SELECT * FROM foo) SELECT * FROM \"MyFoo\""));
+			assertNotNull(parser.parseQuery("WITH \"MyFoo\" AS (SELECT * FROM foo) SELECT * FROM MyFoo"));
+
+			// CASE: correct number of column labels
+			assertNotNull(parser.parseQuery("WITH MyFoo(col1, col2, col3) AS (SELECT * FROM foo) SELECT * FROM MyFoo"));
+
+			// CASE: reference between WITH clause in the correct order
+			assertNotNull(parser.parseQuery("WITH MyFoo AS (SELECT * FROM foo), MyOtherFoo AS (SELECT * FROM MyFoo WHERE colS IS NULL) SELECT * FROM MyOtherFoo"));
+
+		} catch(Exception ex) {
+			ex.printStackTrace();
+			fail("Unexpected error while parsing+checking a valid ADQL query! (see console for more details)");
+		}
+
+		// CASE: CTE case sensibility not respected
+		try {
+			parser.parseQuery("WITH \"MyFoo\" AS (SELECT * FROM foo) SELECT * FROM \"myfoo\"");
+			fail("WITH item's label is case sensitive....references to this CTE should also be case sensitive.");
+		} catch(Exception ex) {
+			assertEquals(UnresolvedIdentifiersException.class, ex.getClass());
+			assertEquals("1 unresolved identifiers: myfoo [l.1 c.51 - l.1 c.58]!\n  - Unknown table \"\"myfoo\"\" !", ex.getMessage());
+		}
+
+		// CASE: less column labels than available columns
+		try {
+			parser.parseQuery("WITH MyFoo(col1) AS (SELECT * FROM foo) SELECT * FROM MyFoo");
+			fail("WITH item's label is case sensitive....references to this CTE should also be case sensitive.");
+		} catch(Exception ex) {
+			assertEquals(UnresolvedIdentifiersException.class, ex.getClass());
+			assertEquals("1 unresolved identifiers!\n  - The WITH query \"MyFoo\" specifies LESS columns (1) than available (3)!", ex.getMessage());
+		}
+		// CASE: more column labels than available columns
+		try {
+			parser.parseQuery("WITH MyFoo(col1, col2, col3, col4) AS (SELECT * FROM foo) SELECT * FROM MyFoo");
+			fail("WITH item's label is case sensitive....references to this CTE should also be case sensitive.");
+		} catch(Exception ex) {
+			assertEquals(UnresolvedIdentifiersException.class, ex.getClass());
+			assertEquals("1 unresolved identifiers!\n  - The WITH query \"MyFoo\" specifies MORE columns (4) than available (3)!", ex.getMessage());
+		}
+
+	}
+
 	@Test
 	public void testSplitTableName() {
 		String[] names = DefaultDBTable.splitTableName("foo");
diff --git a/test/adql/db/TestDBIdentifier.java b/test/adql/db/TestDBIdentifier.java
new file mode 100644
index 0000000000000000000000000000000000000000..20355500423213964fc27b3cdb175ad16594b090
--- /dev/null
+++ b/test/adql/db/TestDBIdentifier.java
@@ -0,0 +1,148 @@
+package adql.db;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import org.junit.Test;
+
+public class TestDBIdentifier {
+
+	@Test
+	public void testIsDelimited() {
+		// CASE: All correctly delimited names
+		assertTrue(DBIdentifier.isDelimited("\"\""));
+		assertTrue(DBIdentifier.isDelimited("\" \""));
+		assertTrue(DBIdentifier.isDelimited("\"a\""));
+		assertTrue(DBIdentifier.isDelimited("\"\"\"\""));
+		assertTrue(DBIdentifier.isDelimited("\"foo.bar\""));
+		assertTrue(DBIdentifier.isDelimited("\"foo\"\".\"\"bar\""));
+
+		// CASE: NOT delimited names
+		assertFalse(DBIdentifier.isDelimited(null));
+		assertFalse(DBIdentifier.isDelimited(""));
+		assertFalse(DBIdentifier.isDelimited("foo"));
+		assertFalse(DBIdentifier.isDelimited("\"foo"));
+		assertFalse(DBIdentifier.isDelimited("foo\""));
+		assertFalse(DBIdentifier.isDelimited("\"foo\".\"bar\""));
+	}
+
+	@Test
+	public void testNormalize() {
+		// CASE: NULL, empty string, delimited empty string => NULL
+		for(String str : new String[]{ null, "", "  ", "  \t \r  \n ", "\"\"", "  \"\" ", "\" \t \n \r  \"" })
+			assertNull(DBIdentifier.normalize(str));
+
+		// CASE: Non-delimited string => same, just trimmed
+		assertEquals("IDent", DBIdentifier.normalize(" \t IDent  \n"));
+		assertEquals("ID\"ent\"", DBIdentifier.normalize(" \t ID\"ent\"  \n"));
+		assertEquals("\" ID\"ent\" \"", DBIdentifier.normalize("\" ID\"ent\" \""));
+
+		// CASE: Delimited string => remove double quotes
+		assertEquals("IDent", DBIdentifier.normalize("\"IDent\""));
+		assertEquals(" IDent ", DBIdentifier.normalize(" \t \" IDent \" \n"));
+		assertEquals(" ID\"ent\" ", DBIdentifier.normalize("\" ID\"\"ent\"\" \""));
+	}
+
+	@Test
+	public void testDenormalize() {
+		// CASE: NULL => NULL
+		assertNull(DBIdentifier.denormalize(null, true));
+		assertNull(DBIdentifier.denormalize(null, false));
+
+		// CASE: Non-case-sensitive string => exactly same as provided
+		assertEquals(" \t IDent  \n", DBIdentifier.denormalize(" \t IDent  \n", false));
+
+		// CASE: Case-sensitive string => surrounded by double quotes
+		assertEquals("\" ID\"\"ent\"\"\"", DBIdentifier.denormalize(" ID\"ent\"", true));
+	}
+
+	@Test
+	public void testSetADQLName() {
+		DBIdentifier dbid = new DBIdentifier4Test("foo");
+		assertEquals("foo", dbid.adqlName);
+		assertFalse(dbid.adqlCaseSensitive);
+		assertNull(dbid.dbName);
+
+		// CASE: missing ADQL name => NullPointerException
+		for(String str : new String[]{ null, "", "  ", "  \t \r  \n ", "\"\"", "  \"\" ", "\" \t \n \r  \"" }) {
+			try {
+				dbid.setADQLName(str);
+				fail("Setting a NULL or empty ADQL name should have failed with a NullPointerException!");
+			} catch(Exception ex) {
+				assertEquals(NullPointerException.class, ex.getClass());
+				assertEquals("Missing ADQL name!", ex.getMessage());
+			}
+		}
+
+		// CASE: Non-delimited ADQL name
+		dbid.setADQLName("Ident");
+		assertEquals("Ident", dbid.getADQLName());
+		assertFalse(dbid.isCaseSensitive());
+		assertNull(dbid.dbName);
+		assertEquals(dbid.getADQLName(), dbid.getDBName());
+
+		// CASE: Delimited ADQL name
+		dbid.setADQLName("\"Ident\"");
+		assertEquals("Ident", dbid.getADQLName());
+		assertTrue(dbid.isCaseSensitive());
+		assertNull(dbid.dbName);
+		assertEquals(dbid.getADQLName(), dbid.getDBName());
+	}
+
+	@Test
+	public void testSetDBName() {
+		DBIdentifier dbid = new DBIdentifier4Test("foo", "dbFoo");
+		assertEquals("foo", dbid.adqlName);
+		assertFalse(dbid.adqlCaseSensitive);
+		assertEquals("dbFoo", dbid.dbName);
+
+		// CASE: missing ADQL name => NullPointerException
+		for(String str : new String[]{ null, "", "  ", "  \t \r  \n ", "\"\"", "  \"\" ", "\" \t \n \r  \"" }) {
+			dbid.setDBName(str);
+			assertNull(dbid.dbName);
+			assertEquals(dbid.getADQLName(), dbid.getDBName());
+		}
+
+		// CASE: Non-delimited DB name
+		dbid.setDBName("Ident");
+		assertEquals("Ident", dbid.dbName);
+		assertEquals(dbid.dbName, dbid.getDBName());
+
+		// CASE: Delimited DB name
+		dbid.setDBName("\"Ident\"");
+		assertEquals("Ident", dbid.getDBName());
+		assertEquals("Ident", dbid.dbName);
+		assertEquals(dbid.dbName, dbid.getDBName());
+	}
+
+	@Test
+	public void testSetCaseSensitive() {
+		DBIdentifier dbid = new DBIdentifier4Test("foo", "dbFoo");
+		assertFalse(dbid.isCaseSensitive());
+
+		// CASE: set case-sensitive
+		dbid.setCaseSensitive(true);
+		assertTrue(dbid.isCaseSensitive());
+
+		// CASE: set INcase-sensitive
+		dbid.setCaseSensitive(false);
+		assertFalse(dbid.isCaseSensitive());
+
+	}
+
+	private final static class DBIdentifier4Test extends DBIdentifier {
+
+		public DBIdentifier4Test(String adqlName, String dbName) throws NullPointerException {
+			super(adqlName, dbName);
+		}
+
+		public DBIdentifier4Test(String adqlName) throws NullPointerException {
+			super(adqlName);
+		}
+
+	}
+
+}
diff --git a/test/adql/db/TestDefaultDBColumn.java b/test/adql/db/TestDefaultDBColumn.java
new file mode 100644
index 0000000000000000000000000000000000000000..3db624c67f760882366fbfe22fa932cf8304d0b3
--- /dev/null
+++ b/test/adql/db/TestDefaultDBColumn.java
@@ -0,0 +1,71 @@
+package adql.db;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+public class TestDefaultDBColumn {
+
+	@Test
+	public void testDefaultDBColumnStringString() {
+		DefaultDBTable table = new DefaultDBTable("table");
+
+		// CASE: No DB name, no case sensitivity
+		DefaultDBColumn col = new DefaultDBColumn("adqlName", table);
+		assertEquals("adqlName", col.getADQLName());
+		assertEquals(col.getADQLName(), col.getDBName());
+		assertFalse(col.isCaseSensitive());
+
+		// CASE: No DB name, case sensitivity
+		col = new DefaultDBColumn("\"adqlName\"", table);
+		assertEquals("adqlName", col.getADQLName());
+		assertEquals(col.getADQLName(), col.getDBName());
+		assertTrue(col.isCaseSensitive());
+
+		// CASE: DB name, no case sensitivity
+		col = new DefaultDBColumn("adqlName", "dbName", table);
+		assertEquals("adqlName", col.getADQLName());
+		assertEquals("dbName", col.getDBName());
+		assertFalse(col.isCaseSensitive());
+
+		// CASE: DN name, case sensitivity
+		col = new DefaultDBColumn("\"adqlName\"", "dbName", table);
+		assertEquals("adqlName", col.getADQLName());
+		assertEquals("dbName", col.getDBName());
+		assertTrue(col.isCaseSensitive());
+	}
+
+	@Test
+	public void testSetADQLName() {
+		DefaultDBTable table = new DefaultDBTable("table");
+
+		// CASE: no case sensitivity, no DB name
+		DefaultDBColumn col = new DefaultDBColumn("adqlName", table);
+		assertEquals("adqlName", col.getADQLName());
+		assertEquals(col.getADQLName(), col.getDBName());
+		assertFalse(col.isCaseSensitive());
+
+		// CASE: undelimited name => OK
+		col.setADQLName("myColumn");
+		assertEquals("myColumn", col.getADQLName());
+		assertFalse(col.isCaseSensitive());
+
+		// CASE: delimited name => stored undelimited
+		col.setADQLName("\"MyColumn\"");
+		assertEquals("MyColumn", col.getADQLName());
+		assertTrue(col.isCaseSensitive());
+
+		// CASE: missing DB name => ERROR!
+		for(String n : new String[]{ null, "", " 	 ", "\"\"", "\"  \"" }) {
+			try {
+				new DefaultDBColumn(n, table);
+			} catch(Exception ex) {
+				assertEquals(NullPointerException.class, ex.getClass());
+				assertEquals("Missing ADQL name!", ex.getMessage());
+			}
+		}
+	}
+
+}
diff --git a/test/adql/db/TestDefaultDBTable.java b/test/adql/db/TestDefaultDBTable.java
index 016ee2bade4a7be8c80011b3aa4dca14ae739aa3..539f8535a88ba3db7a769507254f80ae9468b000 100644
--- a/test/adql/db/TestDefaultDBTable.java
+++ b/test/adql/db/TestDefaultDBTable.java
@@ -9,29 +9,39 @@ import org.junit.Test;
 public class TestDefaultDBTable {
 
 	@Test
-	public void testIsDelimited() {
-		// CASE: All correctly delimited names
-		assertTrue(DefaultDBTable.isDelimited("\"\""));
-		assertTrue(DefaultDBTable.isDelimited("\" \""));
-		assertTrue(DefaultDBTable.isDelimited("\"a\""));
-		assertTrue(DefaultDBTable.isDelimited("\"\"\"\""));
-		assertTrue(DefaultDBTable.isDelimited("\"foo.bar\""));
-		assertTrue(DefaultDBTable.isDelimited("\"foo\"\".\"\"bar\""));
-
-		// CASE: NOT delimited names
-		assertFalse(DefaultDBTable.isDelimited(null));
-		assertFalse(DefaultDBTable.isDelimited(""));
-		assertFalse(DefaultDBTable.isDelimited("foo"));
-		assertFalse(DefaultDBTable.isDelimited("\"foo"));
-		assertFalse(DefaultDBTable.isDelimited("foo\""));
-		assertFalse(DefaultDBTable.isDelimited("\"foo\".\"bar\""));
+	public void testDefaultDBTableStringString() {
+		// CASE: No DN name, no case sensitivity
+		DefaultDBTable table = new DefaultDBTable("adqlName");
+		assertEquals("adqlName", table.getADQLName());
+		assertEquals(table.getADQLName(), table.getDBName());
+		assertFalse(table.isCaseSensitive());
+
+		// CASE: No DB name, case sensitivity
+		table = new DefaultDBTable("\"adqlName\"");
+		assertEquals("adqlName", table.getADQLName());
+		assertEquals(table.getADQLName(), table.getDBName());
+		assertTrue(table.isCaseSensitive());
+
+		// CASE: DB name, no case sensitivity
+		table = new DefaultDBTable("adqlName", "dbName");
+		assertEquals("adqlName", table.getADQLName());
+		assertEquals("dbName", table.getDBName());
+		assertFalse(table.isCaseSensitive());
+
+		// CASE: DB name, case sensitivity
+		table = new DefaultDBTable("\"adqlName\"", "dbName");
+		assertEquals("adqlName", table.getADQLName());
+		assertEquals("dbName", table.getDBName());
+		assertTrue(table.isCaseSensitive());
 	}
 
 	@Test
 	public void testSetADQLName() {
 
-		DefaultDBTable table = new DefaultDBTable("dbName");
-		assertEquals(table.getDBName(), table.getADQLName());
+		// CASE: no case sensitivity, no DB name
+		DefaultDBTable table = new DefaultDBTable("adqlName");
+		assertEquals("adqlName", table.getADQLName());
+		assertEquals(table.getADQLName(), table.getDBName());
 		assertFalse(table.isCaseSensitive());
 
 		// CASE: undelimited name => OK
@@ -39,9 +49,9 @@ public class TestDefaultDBTable {
 		assertEquals("myTable", table.getADQLName());
 		assertFalse(table.isCaseSensitive());
 
-		// CASE: No name => use the DBName
-		table.setADQLName(null);
-		assertEquals(table.getDBName(), table.getADQLName());
+		// CASE: No DB name => use the ADQLName
+		table.setDBName(null);
+		assertEquals(table.getADQLName(), table.getDBName());
 		assertFalse(table.isCaseSensitive());
 
 		// CASE: delimited name => stored undelimited
@@ -49,22 +59,26 @@ public class TestDefaultDBTable {
 		assertEquals("MyTable", table.getADQLName());
 		assertTrue(table.isCaseSensitive());
 
-		// CASE: Empty string => use the DBName (as name=NULL)
-		table.setADQLName("");
-		assertEquals(table.getDBName(), table.getADQLName());
-		assertFalse(table.isCaseSensitive());
+		// CASE: Empty string => use the ADQLName (as name=NULL)
+		table.setDBName("");
+		assertEquals(table.getADQLName(), table.getDBName());
+		assertTrue(table.isCaseSensitive());
 
-		// CASE: dbName delimited and no ADQL name => adqlName = undelimited dbName
-		table = new DefaultDBTable("\"DBName\"");
-		table.setADQLName(null);
-		assertEquals("DBName", table.getADQLName());
+		// CASE: adqlName delimited and no DB name => dbName = undelimited adqlName
+		table = new DefaultDBTable("\"ADQLName\"");
+		table.setDBName(null);
+		assertEquals("ADQLName", table.getDBName());
 		assertTrue(table.isCaseSensitive());
 
-		// CASE: dbName delimited but empty and no ADQL name => adqlName = delimited dbName
-		table = new DefaultDBTable("\"  \"");
-		table.setADQLName(null);
-		assertEquals(table.getDBName(), table.getADQLName());
-		assertFalse(table.isCaseSensitive());
+		// CASE: missing DB name => ERROR!
+		for(String n : new String[]{ null, "", " 	 ", "\"\"", "\"  \"" }) {
+			try {
+				new DefaultDBTable(n);
+			} catch(Exception ex) {
+				assertEquals(NullPointerException.class, ex.getClass());
+				assertEquals("Missing ADQL name!", ex.getMessage());
+			}
+		}
 	}
 
 }
diff --git a/test/adql/db/TestIdentifierCaseSensitivity.java b/test/adql/db/TestIdentifierCaseSensitivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..dc7a0b77142b5638259b807e53c2b6ec0ced7e24
--- /dev/null
+++ b/test/adql/db/TestIdentifierCaseSensitivity.java
@@ -0,0 +1,463 @@
+package adql.db;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.Test;
+
+import adql.db.exception.UnresolvedIdentifiersException;
+import adql.parser.ADQLParser;
+import adql.parser.ADQLParser.ADQLVersion;
+import adql.query.ADQLQuery;
+import adql.translator.PostgreSQLTranslator;
+
+public class TestIdentifierCaseSensitivity {
+
+	@Test
+	public void testDBTables() {
+		List<DBTable> testTables = new ArrayList<DBTable>(2);
+		testTables.add(new DefaultDBTable("NCS_ADQLTable", "dbTable1"));
+		testTables.add(new DefaultDBTable("\"CS_ADQLTable\"", "dbTable2"));
+
+		for(ADQLVersion adqlVersion : ADQLVersion.values()) {
+			ADQLParser parser = new ADQLParser(adqlVersion);
+			parser.setQueryChecker(new DBChecker(testTables));
+
+			/* CASES: NON-CASE SENSITIVE TABLE NAME.
+			 *   It should match only if:
+			 *     - in ANY case NOT between double quotes
+			 *     - OR in LOWER case BETWEEN double quotes.  */
+			try {
+				assertNotNull(parser.parseQuery("SELECT * FROM ncs_adqltable"));
+				assertNotNull(parser.parseQuery("SELECT * FROM NCS_ADQLTABLE"));
+				assertNotNull(parser.parseQuery("SELECT * FROM NCS_ADQLTable"));
+				assertNotNull(parser.parseQuery("SELECT * FROM \"ncs_adqltable\""));
+			} catch(Exception ex) {
+				ex.printStackTrace();
+				fail("[ADQL-" + adqlVersion + "] This non-case sensitive table should have matched! (see console for more details)");
+			}
+			try {
+				parser.parseQuery("SELECT * FROM \"NCS_ADQLTable\"");
+				fail("[ADQL-" + adqlVersion + "] The table name is NOT case sensitive. This test should have failed if the table name is not fully in lowercase.");
+			} catch(Exception ex) {
+				assertEquals(UnresolvedIdentifiersException.class, ex.getClass());
+				assertEquals("1 unresolved identifiers: NCS_ADQLTable [l.1 c.15 - l.1 c.30]!\n  - Unknown table \"\"NCS_ADQLTable\"\" !", ex.getMessage());
+			}
+
+			/* CASES: CASE SENSITIVE TABLE NAME.
+			 *   It should match only if:
+			 *     - in ANY case NOT between double quotes (if non-ambiguous name)
+			 *     - OR in EXACT case BETWEEN double quotes.  */
+			try {
+				assertNotNull(parser.parseQuery("SELECT * FROM cs_adqltable"));
+				assertNotNull(parser.parseQuery("SELECT * FROM CS_ADQLTABLE"));
+				assertNotNull(parser.parseQuery("SELECT * FROM CS_ADQLTable"));
+				assertNotNull(parser.parseQuery("SELECT * FROM \"CS_ADQLTable\""));
+			} catch(Exception ex) {
+				ex.printStackTrace();
+				fail("[ADQL-" + adqlVersion + "]  This case sensitive table should have matched! (see console for more details)");
+			}
+			try {
+				parser.parseQuery("SELECT * FROM \"cs_adqltable\"");
+				fail("[ADQL-" + adqlVersion + "]  The table name is case sensitive. This test should have failed if the table name is not written with the exact case.");
+			} catch(Exception ex) {
+				assertEquals(UnresolvedIdentifiersException.class, ex.getClass());
+				assertEquals("1 unresolved identifiers: cs_adqltable [l.1 c.15 - l.1 c.29]!\n  - Unknown table \"\"cs_adqltable\"\" !", ex.getMessage());
+			}
+		}
+	}
+
+	@Test
+	public void testDBColumns() {
+		DefaultDBTable testT = new DefaultDBTable("adqlTable", "dbTableName");
+		testT.addColumn(new DefaultDBColumn("NCS_ADQLColumn", "dbCol1", testT));
+		testT.addColumn(new DefaultDBColumn("\"CS_ADQLColumn\"", "dbCol2", testT));
+
+		List<DBTable> testTables = new ArrayList<DBTable>(1);
+		testTables.add(testT);
+
+		for(ADQLVersion adqlVersion : ADQLVersion.values()) {
+			ADQLParser parser = new ADQLParser(adqlVersion);
+			parser.setQueryChecker(new DBChecker(testTables));
+
+			/* CASES: NON-CASE SENSITIVE COLUMN NAME.
+			 *   It should match only if:
+			 *     - in ANY case NOT between double quotes
+			 *     - OR in LOWER case BETWEEN double quotes.  */
+			try {
+				assertNotNull(parser.parseQuery("SELECT ncs_adqlcolumn FROM adqltable"));
+				assertNotNull(parser.parseQuery("SELECT NCS_ADQLCOLUMN FROM adqltable"));
+				assertNotNull(parser.parseQuery("SELECT NCS_ADQLColumn FROM adqltable"));
+				assertNotNull(parser.parseQuery("SELECT \"ncs_adqlcolumn\" FROM adqltable"));
+			} catch(Exception ex) {
+				ex.printStackTrace();
+				fail("[ADQL-" + adqlVersion + "]  This non-case sensitive column should have matched! (see console for more details)");
+			}
+			try {
+				parser.parseQuery("SELECT \"NCS_ADQLColumn\" FROM adqltable");
+				fail("[ADQL-" + adqlVersion + "]  The column name is NOT case sensitive. This test should have failed if the column name is not fully in lowercase.");
+			} catch(Exception ex) {
+				assertEquals(UnresolvedIdentifiersException.class, ex.getClass());
+				assertEquals("1 unresolved identifiers: NCS_ADQLColumn [l.1 c.8 - l.1 c.24]!\n  - Unknown column \"\"NCS_ADQLColumn\"\" !", ex.getMessage());
+			}
+
+			/* CASES: CASE SENSITIVE COLUMN NAME.
+			 *   It should match only if:
+			 *     - in ANY case NOT between double quotes (if non-ambiguous name)
+			 *     - OR in EXACT case BETWEEN double quotes.  */
+			try {
+				assertNotNull(parser.parseQuery("SELECT cs_adqlcolumn FROM adqltable"));
+				assertNotNull(parser.parseQuery("SELECT CS_ADQLCOLUMN FROM adqltable"));
+				assertNotNull(parser.parseQuery("SELECT CS_ADQLColumn FROM adqltable"));
+				assertNotNull(parser.parseQuery("SELECT \"CS_ADQLColumn\" FROM adqltable"));
+			} catch(Exception ex) {
+				ex.printStackTrace();
+				fail("[ADQL-" + adqlVersion + "]  This case sensitive column should have matched! (see console for more details)");
+			}
+			try {
+				parser.parseQuery("SELECT \"cs_adqlcolumn\" FROM adqltable");
+				fail("[ADQL-" + adqlVersion + "]  The column name is case sensitive. This test should have failed if the column name is not written with the exact case.");
+			} catch(Exception ex) {
+				assertEquals(UnresolvedIdentifiersException.class, ex.getClass());
+				assertEquals("1 unresolved identifiers: cs_adqlcolumn [l.1 c.8 - l.1 c.23]!\n  - Unknown column \"\"cs_adqlcolumn\"\" !", ex.getMessage());
+			}
+		}
+	}
+
+	@Test
+	public void testFROMNames() {
+		List<DBTable> testTables = new ArrayList<DBTable>();
+		testTables.add(new DefaultDBTable("adqlTable", "dbTable"));
+
+		for(ADQLVersion adqlVersion : ADQLVersion.values()) {
+			ADQLParser parser = new ADQLParser(adqlVersion);
+			parser.setQueryChecker(new DBChecker(testTables));
+
+			/* CASES: NON-CASE SENSITIVE TABLE NAME.
+			 *   (see testDBTables())  */
+			try {
+				// SUB-CASE: table reference
+				assertNotNull(parser.parseQuery("SELECT atable.* FROM adqltable AS aTable"));
+				assertNotNull(parser.parseQuery("SELECT ATABLE.* FROM adqltable AS aTable"));
+				assertNotNull(parser.parseQuery("SELECT aTable.* FROM adqltable AS aTable"));
+				assertNotNull(parser.parseQuery("SELECT \"atable\".* FROM adqltable AS aTable"));
+				// SUB-CASE: sub-query
+				assertNotNull(parser.parseQuery("SELECT atable.* FROM (SELECT * FROM adqltable) AS aTable"));
+				assertNotNull(parser.parseQuery("SELECT ATABLE.* FROM (SELECT * FROM adqltable) AS aTable"));
+				assertNotNull(parser.parseQuery("SELECT aTable.* FROM (SELECT * FROM adqltable) AS aTable"));
+				assertNotNull(parser.parseQuery("SELECT \"atable\".* FROM (SELECT * FROM adqltable) AS aTable"));
+			} catch(Exception ex) {
+				ex.printStackTrace();
+				fail("[ADQL-" + adqlVersion + "]  This non-case sensitive table should have matched! (see console for more details)");
+			}
+			try {
+				// SUB-CASE: table reference
+				parser.parseQuery("SELECT \"aTable\".* FROM adqltable AS aTable");
+				fail("[ADQL-" + adqlVersion + "]  The table name is NOT case sensitive. This test should have failed if the table name is not fully in lowercase.");
+			} catch(Exception ex) {
+				assertEquals(UnresolvedIdentifiersException.class, ex.getClass());
+				assertEquals("1 unresolved identifiers: aTable [l.1 c.8 - l.1 c.16]!\n  - Unknown table \"\"aTable\"\" !", ex.getMessage());
+			}
+			try {
+				// SUB-CASE: sub-query
+				parser.parseQuery("SELECT \"aTable\".* FROM (SELECT * FROM adqltable) AS aTable");
+				fail("[ADQL-" + adqlVersion + "]  The table name is NOT case sensitive. This test should have failed if the table name is not fully in lowercase.");
+			} catch(Exception ex) {
+				assertEquals(UnresolvedIdentifiersException.class, ex.getClass());
+				assertEquals("1 unresolved identifiers: aTable [l.1 c.8 - l.1 c.16]!\n  - Unknown table \"\"aTable\"\" !", ex.getMessage());
+			}
+
+			/* CASES: CASE SENSITIVE TABLE NAME.
+			 *   (see testDBTables())  */
+			try {
+				// SUB-CASE: table reference
+				assertNotNull(parser.parseQuery("SELECT atable.* FROM adqltable AS \"aTable\""));
+				assertNotNull(parser.parseQuery("SELECT ATABLE.* FROM adqltable AS \"aTable\""));
+				assertNotNull(parser.parseQuery("SELECT aTable.* FROM adqltable AS \"aTable\""));
+				assertNotNull(parser.parseQuery("SELECT \"aTable\".* FROM adqltable AS \"aTable\""));
+				// SUB-CASE: sub-query
+				assertNotNull(parser.parseQuery("SELECT atable.* FROM (SELECT * FROM adqltable) AS \"aTable\""));
+				assertNotNull(parser.parseQuery("SELECT ATABLE.* FROM (SELECT * FROM adqltable) AS \"aTable\""));
+				assertNotNull(parser.parseQuery("SELECT aTable.* FROM (SELECT * FROM adqltable) AS \"aTable\""));
+				assertNotNull(parser.parseQuery("SELECT \"aTable\".* FROM (SELECT * FROM adqltable) AS \"aTable\""));
+			} catch(Exception ex) {
+				ex.printStackTrace();
+				fail("[ADQL-" + adqlVersion + "]  This case sensitive table should have matched! (see console for more details)");
+			}
+			try {
+				// SUB-CASE: table reference
+				parser.parseQuery("SELECT \"atable\".* FROM adqltable AS \"aTable\"");
+				fail("[ADQL-" + adqlVersion + "]  The table name is case sensitive. This test should have failed if the table name is not written with the exact case.");
+			} catch(Exception ex) {
+				assertEquals(UnresolvedIdentifiersException.class, ex.getClass());
+				assertEquals("1 unresolved identifiers: atable [l.1 c.8 - l.1 c.16]!\n  - Unknown table \"\"atable\"\" !", ex.getMessage());
+			}
+			try {
+				// SUB-CASE: sub-query
+				parser.parseQuery("SELECT \"atable\".* FROM (SELECT * FROM adqltable) AS \"aTable\"");
+				fail("[ADQL-" + adqlVersion + "]  The table name is case sensitive. This test should have failed if the table name is not written with the exact case.");
+			} catch(Exception ex) {
+				assertEquals(UnresolvedIdentifiersException.class, ex.getClass());
+				assertEquals("1 unresolved identifiers: atable [l.1 c.8 - l.1 c.16]!\n  - Unknown table \"\"atable\"\" !", ex.getMessage());
+			}
+		}
+	}
+
+	@Test
+	public void testCTENames() {
+		List<DBTable> testTables = new ArrayList<DBTable>();
+		testTables.add(new DefaultDBTable("adqlTable", "dbTable"));
+
+		ADQLParser parser = new ADQLParser(ADQLVersion.V2_1);
+		parser.setQueryChecker(new DBChecker(testTables));
+
+		/* CASES: NON-CASE SENSITIVE TABLE NAME.
+		 *   (see testDBTables())  */
+		try {
+			assertNotNull(parser.parseQuery("WITH aTable AS (SELECT * FROM adqltable) SELECT * FROM atable"));
+			assertNotNull(parser.parseQuery("WITH aTable AS (SELECT * FROM adqltable) SELECT * FROM ATABLE"));
+			assertNotNull(parser.parseQuery("WITH aTable AS (SELECT * FROM adqltable) SELECT * FROM aTable"));
+			assertNotNull(parser.parseQuery("WITH aTable AS (SELECT * FROM adqltable) SELECT * FROM \"atable\""));
+		} catch(Exception ex) {
+			ex.printStackTrace();
+			fail("This non-case sensitive table name should have matched! (see console for more details)");
+		}
+		try {
+			parser.parseQuery("WITH aTable AS (SELECT * FROM adqltable) SELECT * FROM \"aTable\"");
+			fail("The table name is NOT case sensitive. This test should have failed if the table name is not fully in lowercase.");
+		} catch(Exception ex) {
+			assertEquals(UnresolvedIdentifiersException.class, ex.getClass());
+			assertEquals("1 unresolved identifiers: aTable [l.1 c.56 - l.1 c.64]!\n  - Unknown table \"\"aTable\"\" !", ex.getMessage());
+		}
+
+		/* CASES: CASE SENSITIVE TABLE NAME.
+		 *   (see testDBTables())  */
+		try {
+			assertNotNull(parser.parseQuery("WITH \"aTable\" AS (SELECT * FROM adqltable) SELECT * FROM atable"));
+			assertNotNull(parser.parseQuery("WITH \"aTable\" AS (SELECT * FROM adqltable) SELECT * FROM ATABLE"));
+			assertNotNull(parser.parseQuery("WITH \"aTable\" AS (SELECT * FROM adqltable) SELECT * FROM aTable"));
+			assertNotNull(parser.parseQuery("WITH \"aTable\" AS (SELECT * FROM adqltable) SELECT * FROM \"aTable\""));
+		} catch(Exception ex) {
+			ex.printStackTrace();
+			fail("This case sensitive table should have matched! (see console for more details)");
+		}
+		try {
+			parser.parseQuery("WITH \"aTable\" AS (SELECT * FROM adqltable) SELECT * FROM \"atable\"");
+			fail("The table name is case sensitive. This test should have failed if the table name is not written with the exact case.");
+		} catch(Exception ex) {
+			assertEquals(UnresolvedIdentifiersException.class, ex.getClass());
+			assertEquals("1 unresolved identifiers: atable [l.1 c.58 - l.1 c.66]!\n  - Unknown table \"\"atable\"\" !", ex.getMessage());
+		}
+	}
+
+	@Test
+	public void testTranslateDBTables() {
+		List<DBTable> testTables = new ArrayList<DBTable>(4);
+		testTables.add(new DefaultDBTable("Table1", "dbTable1"));
+		testTables.add(new DefaultDBTable("\"Table2\"", "dbTable2"));
+		testTables.add(new DefaultDBTable("Table3"));
+		testTables.add(new DefaultDBTable("\"Table4\""));
+
+		for(ADQLVersion version : ADQLVersion.values()) {
+			ADQLParser parser = new ADQLParser(version);
+			parser.setQueryChecker(new DBChecker(testTables));
+			PostgreSQLTranslator trCS = new PostgreSQLTranslator(true);
+			PostgreSQLTranslator trCI = new PostgreSQLTranslator(false);
+
+			// CASE: Non-case-sensitive ADQL name, Specified DB name:
+			try {
+				ADQLQuery query = parser.parseQuery("SELECT * FROM table1");
+				assertEquals("SELECT *\nFROM table1", query.toADQL());
+				assertEquals("SELECT *\nFROM \"dbTable1\"", trCS.translate(query));
+				assertEquals("SELECT *\nFROM dbTable1", trCI.translate(query));
+			} catch(Exception ex) {
+				ex.printStackTrace();
+				fail("Unexpected exception! (see console for more details)");
+			}
+
+			// CASE: Case-sensitive ADQL name, Specified DB name:
+			try {
+				ADQLQuery query = parser.parseQuery("SELECT * FROM \"Table2\"");
+				assertEquals("SELECT *\nFROM \"Table2\"", query.toADQL());
+				assertEquals("SELECT *\nFROM \"dbTable2\"", trCS.translate(query));
+				assertEquals("SELECT *\nFROM dbTable2", trCI.translate(query));
+			} catch(Exception ex) {
+				ex.printStackTrace();
+				fail("Unexpected exception! (see console for more details)");
+			}
+
+			// CASE: Non-case-sensitive ADQL name, UNspecified DB name:
+			try {
+				ADQLQuery query = parser.parseQuery("SELECT * FROM table3");
+				assertEquals("SELECT *\nFROM table3", query.toADQL());
+				assertEquals("SELECT *\nFROM \"Table3\"", trCS.translate(query));
+				assertEquals("SELECT *\nFROM Table3", trCI.translate(query));
+			} catch(Exception ex) {
+				ex.printStackTrace();
+				fail("Unexpected exception! (see console for more details)");
+			}
+
+			// CASE: Case-sensitive ADQL name, UNspecified DB name:
+			try {
+				ADQLQuery query = parser.parseQuery("SELECT * FROM table4");
+				assertEquals("SELECT *\nFROM table4", query.toADQL());
+				assertEquals("SELECT *\nFROM \"Table4\"", trCS.translate(query));
+				assertEquals("SELECT *\nFROM Table4", trCI.translate(query));
+			} catch(Exception ex) {
+				ex.printStackTrace();
+				fail("Unexpected exception! (see console for more details)");
+			}
+		}
+	}
+
+	@Test
+	public void testTranslateFROMNames() {
+		List<DBTable> testTables = new ArrayList<DBTable>(1);
+		DefaultDBTable t = new DefaultDBTable("Table1", "dbTable1");
+		t.addColumn(new DefaultDBColumn("col1", t));
+		testTables.add(t);
+
+		for(ADQLVersion version : ADQLVersion.values()) {
+			ADQLParser parser = new ADQLParser(version);
+			parser.setQueryChecker(new DBChecker(testTables));
+			PostgreSQLTranslator trCS = new PostgreSQLTranslator(true);
+			PostgreSQLTranslator trCI = new PostgreSQLTranslator(false);
+
+			// CASE: Non-case-sensitive lower-case name:
+			try {
+				ADQLQuery query = parser.parseQuery("SELECT T1.*, t1.col1 FROM table1 AS t1");
+				assertEquals("SELECT T1.* , t1.col1\nFROM table1 AS t1", query.toADQL());
+				assertEquals("SELECT \"t1\".\"col1\" AS \"col1\" , \"t1\".\"col1\" AS \"col1\"\nFROM \"dbTable1\" AS \"t1\"", trCS.translate(query));
+				assertEquals("SELECT t1.col1 AS \"col1\" , t1.col1 AS \"col1\"\nFROM dbTable1 AS \"t1\"", trCI.translate(query));
+			} catch(Exception ex) {
+				ex.printStackTrace();
+				fail("Unexpected exception! (see console for more details)");
+			}
+
+			// CASE: Non-case-sensitive mixed-case name:
+			try {
+				ADQLQuery query = parser.parseQuery("SELECT T1.*, t1.col1 FROM table1 AS T1");
+				assertEquals("SELECT T1.* , t1.col1\nFROM table1 AS T1", query.toADQL());
+				assertEquals("SELECT \"t1\".\"col1\" AS \"col1\" , \"t1\".\"col1\" AS \"col1\"\nFROM \"dbTable1\" AS \"t1\"", trCS.translate(query));
+				assertEquals("SELECT t1.col1 AS \"col1\" , t1.col1 AS \"col1\"\nFROM dbTable1 AS \"t1\"", trCI.translate(query));
+			} catch(Exception ex) {
+				ex.printStackTrace();
+				fail("Unexpected exception! (see console for more details)");
+			}
+
+			// CASE: Case-sensitive ADQL name:
+			try {
+				ADQLQuery query = parser.parseQuery("SELECT T1.*, t1.col1 FROM table1 AS \"T1\"");
+				assertEquals("SELECT T1.* , t1.col1\nFROM table1 AS \"T1\"", query.toADQL());
+				assertEquals("SELECT \"T1\".\"col1\" AS \"col1\" , \"T1\".\"col1\" AS \"col1\"\nFROM \"dbTable1\" AS \"T1\"", trCS.translate(query));
+				assertEquals("SELECT T1.col1 AS \"col1\" , T1.col1 AS \"col1\"\nFROM dbTable1 AS \"T1\"", trCI.translate(query));
+			} catch(Exception ex) {
+				ex.printStackTrace();
+				fail("Unexpected exception! (see console for more details)");
+			}
+		}
+	}
+
+	@Test
+	public void testTranslateCTENames() {
+		List<DBTable> testTables = new ArrayList<DBTable>(1);
+		DefaultDBTable t = new DefaultDBTable("Table1", "dbTable1");
+		t.addColumn(new DefaultDBColumn("col1", t));
+		testTables.add(t);
+
+		ADQLParser parser = new ADQLParser(ADQLVersion.V2_1);
+		parser.setQueryChecker(new DBChecker(testTables));
+		PostgreSQLTranslator trCS = new PostgreSQLTranslator(true);
+		PostgreSQLTranslator trCI = new PostgreSQLTranslator(false);
+
+		// CASE: Non-case-sensitive lower-case name:
+		try {
+			ADQLQuery query = parser.parseQuery("WITH t1 AS (SELECT * FROM table1) SELECT * FROM t1");
+			assertEquals("WITH t1 AS (\nSELECT *\nFROM table1\n)\nSELECT *\nFROM t1", query.toADQL());
+			assertEquals("WITH \"t1\"(\"col1\") AS (\nSELECT \"dbTable1\".\"col1\" AS \"col1\"\nFROM \"dbTable1\"\n)\nSELECT \"t1\".\"col1\" AS \"col1\"\nFROM \"t1\"", trCS.translate(query));
+			assertEquals("WITH \"t1\"(\"col1\") AS (\nSELECT dbTable1.col1 AS \"col1\"\nFROM dbTable1\n)\nSELECT t1.col1 AS \"col1\"\nFROM t1", trCI.translate(query));
+		} catch(Exception ex) {
+			ex.printStackTrace();
+			fail("Unexpected exception! (see console for more details)");
+		}
+
+		// CASE: Non-case-sensitive mixed-case name:
+		try {
+			ADQLQuery query = parser.parseQuery("WITH T1 AS (SELECT * FROM table1) SELECT * FROM t1");
+			assertEquals("WITH T1 AS (\nSELECT *\nFROM table1\n)\nSELECT *\nFROM t1", query.toADQL());
+			assertEquals("WITH \"t1\"(\"col1\") AS (\nSELECT \"dbTable1\".\"col1\" AS \"col1\"\nFROM \"dbTable1\"\n)\nSELECT \"t1\".\"col1\" AS \"col1\"\nFROM \"t1\"", trCS.translate(query));
+			assertEquals("WITH \"t1\"(\"col1\") AS (\nSELECT dbTable1.col1 AS \"col1\"\nFROM dbTable1\n)\nSELECT t1.col1 AS \"col1\"\nFROM t1", trCI.translate(query));
+		} catch(Exception ex) {
+			ex.printStackTrace();
+			fail("Unexpected exception! (see console for more details)");
+		}
+
+		// CASE: Case-sensitive ADQL name:
+		try {
+			ADQLQuery query = parser.parseQuery("WITH \"T1\" AS (SELECT * FROM table1) SELECT * FROM t1");
+			assertEquals("WITH \"T1\" AS (\nSELECT *\nFROM table1\n)\nSELECT *\nFROM t1", query.toADQL());
+			assertEquals("WITH \"T1\"(\"col1\") AS (\nSELECT \"dbTable1\".\"col1\" AS \"col1\"\nFROM \"dbTable1\"\n)\nSELECT \"T1\".\"col1\" AS \"col1\"\nFROM \"T1\"", trCS.translate(query));
+			assertEquals("WITH \"T1\"(\"col1\") AS (\nSELECT dbTable1.col1 AS \"col1\"\nFROM dbTable1\n)\nSELECT T1.col1 AS \"col1\"\nFROM T1", trCI.translate(query));
+		} catch(Exception ex) {
+			ex.printStackTrace();
+			fail("Unexpected exception! (see console for more details)");
+		}
+	}
+
+	@Test
+	public void testTranslateDBColumns() {
+		List<DBTable> testTables = new ArrayList<DBTable>(1);
+		DefaultDBTable table = new DefaultDBTable("Table1", "dbTable1");
+		table.addColumn(new DefaultDBColumn("Col1", table));
+		table.addColumn(new DefaultDBColumn("\"Col2\"", table));
+		testTables.add(table);
+
+		PostgreSQLTranslator trCS = new PostgreSQLTranslator(true);
+		PostgreSQLTranslator trCI = new PostgreSQLTranslator(false);
+
+		for(ADQLVersion version : ADQLVersion.values()) {
+			ADQLParser parser = new ADQLParser(version);
+			parser.setQueryChecker(new DBChecker(testTables));
+
+			// CASE: Columns from database:
+			try {
+				ADQLQuery query = parser.parseQuery("SELECT COL1, col2, col1 AS \"SuperCol\" FROM table1");
+				assertEquals("SELECT COL1 , col2 , col1 AS \"SuperCol\"\nFROM table1", query.toADQL());
+				assertEquals("SELECT \"dbTable1\".\"Col1\" AS \"col1\" , \"dbTable1\".\"Col2\" AS \"Col2\" , \"dbTable1\".\"Col1\" AS \"SuperCol\"\nFROM \"dbTable1\"", trCS.translate(query));
+				assertEquals("SELECT dbTable1.Col1 AS \"col1\" , dbTable1.Col2 AS \"Col2\" , dbTable1.Col1 AS \"SuperCol\"\nFROM dbTable1", trCI.translate(query));
+			} catch(Exception ex) {
+				ex.printStackTrace();
+				fail("Unexpected exception! (see console for more details)");
+			}
+
+			// CASE: Columns from subquery
+			try {
+				ADQLQuery query = parser.parseQuery("SELECT * FROM (SELECT col1, col2, col1 AS Col3, col2 AS \"COL4\" FROM table1) AS table2");
+				assertEquals("SELECT *\nFROM (SELECT col1 , col2 , col1 AS Col3 , col2 AS \"COL4\"\nFROM table1) AS table2", query.toADQL());
+				assertEquals("SELECT \"table2\".\"Col1\" AS \"col1\" , \"table2\".\"Col2\" AS \"Col2\" , \"table2\".\"col3\" AS \"col3\" , \"table2\".\"COL4\" AS \"COL4\"\nFROM (SELECT \"dbTable1\".\"Col1\" AS \"col1\" , \"dbTable1\".\"Col2\" AS \"Col2\" , \"dbTable1\".\"Col1\" AS \"col3\" , \"dbTable1\".\"Col2\" AS \"COL4\"\nFROM \"dbTable1\") AS \"table2\"", trCS.translate(query));
+				assertEquals("SELECT table2.Col1 AS \"col1\" , table2.Col2 AS \"Col2\" , table2.col3 AS \"col3\" , table2.COL4 AS \"COL4\"\nFROM (SELECT dbTable1.Col1 AS \"col1\" , dbTable1.Col2 AS \"Col2\" , dbTable1.Col1 AS \"col3\" , dbTable1.Col2 AS \"COL4\"\nFROM dbTable1) AS \"table2\"", trCI.translate(query));
+			} catch(Exception ex) {
+				ex.printStackTrace();
+				fail("Unexpected exception! (see console for more details)");
+			}
+
+			// CASE: Columns from CTE
+			if (parser.getADQLVersion() != ADQLVersion.V2_0) {
+				try {
+					ADQLQuery query = parser.parseQuery("WITH table3 AS (SELECT COL1, col2, col1 AS \"SuperCol\" FROM table1) SELECT * FROM table3");
+					assertEquals("WITH table3 AS (\nSELECT COL1 , col2 , col1 AS \"SuperCol\"\nFROM table1\n)\nSELECT *\nFROM table3", query.toADQL());
+					assertEquals("WITH \"table3\"(\"col1\",\"Col2\",\"SuperCol\") AS (\nSELECT \"dbTable1\".\"Col1\" AS \"col1\" , \"dbTable1\".\"Col2\" AS \"Col2\" , \"dbTable1\".\"Col1\" AS \"SuperCol\"\nFROM \"dbTable1\"\n)\nSELECT \"table3\".\"col1\" AS \"col1\" , \"table3\".\"Col2\" AS \"Col2\" , \"table3\".\"SuperCol\" AS \"SuperCol\"\nFROM \"table3\"", trCS.translate(query));
+					assertEquals("WITH \"table3\"(\"col1\",\"Col2\",\"SuperCol\") AS (\nSELECT dbTable1.Col1 AS \"col1\" , dbTable1.Col2 AS \"Col2\" , dbTable1.Col1 AS \"SuperCol\"\nFROM dbTable1\n)\nSELECT table3.col1 AS \"col1\" , table3.Col2 AS \"Col2\" , table3.SuperCol AS \"SuperCol\"\nFROM table3", trCI.translate(query));
+				} catch(Exception ex) {
+					ex.printStackTrace();
+					fail("Unexpected exception! (see console for more details)");
+				}
+			}
+		}
+	}
+
+}
diff --git a/test/adql/db/TestSubQueries.java b/test/adql/db/TestSubQueries.java
index ff9e8283eb96aa48832e1ba7993b14319ef68b28..17b63a3e954000e5612988948b858a732c2ad329 100644
--- a/test/adql/db/TestSubQueries.java
+++ b/test/adql/db/TestSubQueries.java
@@ -58,6 +58,7 @@ public class TestSubQueries {
 			adqlParser.setQueryChecker(new DBChecker(esaTables));
 
 			ADQLQuery query = adqlParser.parseQuery("SELECT oid FROM table1 as MyAlias WHERE oid IN (SELECT oid2 FROM table2 WHERE oid2 = myAlias.oid)");
+			assertEquals("SELECT oid\nFROM table1 AS MyAlias\nWHERE oid IN (SELECT oid2\nFROM table2\nWHERE oid2 = myAlias.oid)", query.toADQL());
 			assertEquals("SELECT \"myalias\".\"oid\" AS \"oid\"\nFROM \"public\".\"table1\" AS \"myalias\"\nWHERE \"myalias\".\"oid\" IN (SELECT \"public\".\"table2\".\"oid2\" AS \"oid2\"\nFROM \"public\".\"table2\"\nWHERE \"public\".\"table2\".\"oid2\" = \"myalias\".\"oid\")", (new PostgreSQLTranslator()).translate(query));
 		} catch(Exception ex) {
 			ex.printStackTrace(System.err);
@@ -79,7 +80,8 @@ public class TestSubQueries {
 			adqlParser.setQueryChecker(new DBChecker(esaTables));
 
 			ADQLQuery query = adqlParser.parseQuery("SELECT t.* FROM (SELECT (ra+ra_error) AS x, (dec+dec_error) AS Y, pmra AS \"ProperMotion\" FROM table2) AS t");
-			assertEquals("SELECT \"t\".\"x\" AS \"x\",\"t\".\"y\" AS \"y\",\"t\".\"ProperMotion\" AS \"ProperMotion\"\nFROM (SELECT ((\"public\".\"table2\".\"ra\"+\"public\".\"table2\".\"ra_error\")) AS \"x\" , ((\"public\".\"table2\".\"dec\"+\"public\".\"table2\".\"dec_error\")) AS \"y\" , \"public\".\"table2\".\"pmra\" AS \"ProperMotion\"\nFROM \"public\".\"table2\") AS \"t\"", (new PostgreSQLTranslator()).translate(query));
+			assertEquals("SELECT t.*\nFROM (SELECT (ra+ra_error) AS x , (dec+dec_error) AS Y , pmra AS \"ProperMotion\"\nFROM table2) AS t", query.toADQL());
+			assertEquals("SELECT \"t\".\"x\" AS \"x\" , \"t\".\"y\" AS \"y\" , \"t\".\"ProperMotion\" AS \"ProperMotion\"\nFROM (SELECT ((\"public\".\"table2\".\"ra\"+\"public\".\"table2\".\"ra_error\")) AS \"x\" , ((\"public\".\"table2\".\"dec\"+\"public\".\"table2\".\"dec_error\")) AS \"y\" , \"public\".\"table2\".\"pmra\" AS \"ProperMotion\"\nFROM \"public\".\"table2\") AS \"t\"", (new PostgreSQLTranslator()).translate(query));
 		} catch(Exception ex) {
 			ex.printStackTrace(System.err);
 			fail("No error expected! (see console for more details)");
diff --git a/test/adql/parser/TestADQLParser.java b/test/adql/parser/TestADQLParser.java
index 6743b8ec66d6eb2d2323e680d7c46f1eeb7d3813..c7c2002feca498e4f504069c64fd7b64ce0a856c 100644
--- a/test/adql/parser/TestADQLParser.java
+++ b/test/adql/parser/TestADQLParser.java
@@ -1,7 +1,9 @@
 package adql.parser;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -24,6 +26,7 @@ import adql.parser.grammar.ADQLGrammar200Constants;
 import adql.parser.grammar.ParseException;
 import adql.parser.grammar.Token;
 import adql.query.ADQLQuery;
+import adql.query.WithItem;
 import adql.query.from.ADQLJoin;
 import adql.query.from.ADQLTable;
 import adql.query.operand.ADQLOperand;
@@ -55,6 +58,83 @@ public class TestADQLParser {
 	public void tearDown() throws Exception {
 	}
 
+	@Test
+	public void testWithClause() {
+
+		// CASE: ADQL-2.0 => ERROR
+		ADQLParser parser = new ADQLParser(ADQLVersion.V2_0);
+		try {
+			parser.parseQuery("WITH foo AS (SELECT * FROM bar) SELECT * FROM foo");
+			fail("In ADQL-2.0, the WITH should not be allowed....it does not exist!");
+		} catch(Exception ex) {
+			assertEquals(ParseException.class, ex.getClass());
+			assertEquals(" Encountered \"WITH\". Was expecting: \"SELECT\" \n" + "(HINT: \"WITH\" is not supported in ADQL v2.0, but is however a reserved word. To use it as a column/table/schema name/alias, write it between double quotes.)", ex.getMessage());
+		}
+
+		parser = new ADQLParser(ADQLVersion.V2_1);
+		try {
+
+			// CASE: Same with ADQL-2.1 => OK
+			ADQLQuery query = parser.parseQuery("WITH foo AS (SELECT * FROM bar) SELECT * FROM foo");
+			assertNotNull(query.getWith());
+			assertEquals(1, query.getWith().size());
+			WithItem item = query.getWith().get(0);
+			assertEquals("foo", item.getLabel());
+			assertFalse(item.isLabelCaseSensitive());
+			assertNull(item.getColumnLabels());
+			assertEquals("SELECT *\nFROM bar", item.getQuery().toADQL());
+
+			// CASE: WITH clause with column labels => OK
+			query = parser.parseQuery("WITH foo(id, ra, dec) AS (SELECT col1, col2, col3 FROM bar) SELECT * FROM foo");
+			assertNotNull(query.getWith());
+			assertEquals(1, query.getWith().size());
+			item = query.getWith().get(0);
+			assertEquals("foo", item.getLabel());
+			assertFalse(item.isLabelCaseSensitive());
+			assertNotNull(item.getColumnLabels());
+			assertEquals(3, item.getColumnLabels().size());
+			assertEquals("SELECT col1 , col2 , col3\nFROM bar", item.getQuery().toADQL());
+
+			// CASE: more than 1 WITH clause + CTE's label case sensitivity
+			query = parser.parseQuery("WITH foo(id, ra, dec) AS (SELECT col1, col2, col3 FROM bar), \"Foo2\" AS (SELECT * FROM bar2) SELECT * FROM foo NATURAL JOIN \"Foo2\"");
+			assertNotNull(query.getWith());
+			assertEquals(2, query.getWith().size());
+			item = query.getWith().get(0);
+			assertEquals("foo", item.getLabel());
+			assertFalse(item.isLabelCaseSensitive());
+			assertNotNull(item.getColumnLabels());
+			assertEquals(3, item.getColumnLabels().size());
+			assertEquals("SELECT col1 , col2 , col3\nFROM bar", item.getQuery().toADQL());
+			item = query.getWith().get(1);
+			assertEquals("Foo2", item.getLabel());
+			assertTrue(item.isLabelCaseSensitive());
+			assertNull(item.getColumnLabels());
+			assertEquals("SELECT *\nFROM bar2", item.getQuery().toADQL());
+
+			// CASE: WITH clause inside a WITH clause => OK
+			query = parser.parseQuery("WITH foo(id, ra, dec) AS (WITH innerFoo AS (SELECT col1, col2, col3 FROM bar) SELECT * FROM stars NATURAL JOIN innerFoo) SELECT * FROM foo");
+			assertNotNull(query.getWith());
+			assertEquals(1, query.getWith().size());
+			item = query.getWith().get(0);
+			assertEquals("foo", item.getLabel());
+			assertFalse(item.isLabelCaseSensitive());
+			assertNotNull(item.getColumnLabels());
+			assertEquals(3, item.getColumnLabels().size());
+			assertEquals("WITH innerFoo AS (\nSELECT col1 , col2 , col3\nFROM bar\n)\nSELECT *\nFROM stars NATURAL INNER JOIN innerFoo", item.getQuery().toADQL());
+			assertNotNull(query.getWith().get(0).getQuery().getWith());
+			assertEquals(1, query.getWith().get(0).getQuery().getWith().size());
+			item = query.getWith().get(0).getQuery().getWith().get(0);
+			assertEquals("innerFoo", item.getLabel());
+			assertFalse(item.isLabelCaseSensitive());
+			assertNull(item.getColumnLabels());
+			assertEquals("SELECT col1 , col2 , col3\nFROM bar", item.getQuery().toADQL());
+
+		} catch(Exception ex) {
+			ex.printStackTrace();
+			fail("Unexpected error while parsing a valid query with a WITH clause! (see console for more details)");
+		}
+	}
+
 	@Test
 	public void testConstraintList() {
 		for(ADQLVersion version : ADQLVersion.values()) {
diff --git a/test/adql/query/TestADQLObjectPosition.java b/test/adql/query/TestADQLObjectPosition.java
index 772e4072c83f6176ab1abf91fa453d53ad5d039b..26fb5fbd274944fccec25a2f2d9f41acbf0d7a28 100644
--- a/test/adql/query/TestADQLObjectPosition.java
+++ b/test/adql/query/TestADQLObjectPosition.java
@@ -31,12 +31,15 @@ public class TestADQLObjectPosition {
 	public void testPositionInAllClauses() {
 		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");
+				ADQLQuery query = new ADQLParser(version).parseQuery((version != ADQLVersion.V2_0 ? "WITH bar AS (SELECT * FROM superbar) " : "") + "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;
+						if (obj instanceof ADQLList<?> && ((ADQLList<?>)obj).isEmpty())
+							return false;
+						else
+							return obj.getPosition() == null;
 					}
 				});
 				if (results.hasNext()) {
diff --git a/test/adql/query/TestWithItem.java b/test/adql/query/TestWithItem.java
new file mode 100644
index 0000000000000000000000000000000000000000..57ca9ed00969c77313e9806cf2585320be126fb8
--- /dev/null
+++ b/test/adql/query/TestWithItem.java
@@ -0,0 +1,154 @@
+package adql.query;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.util.Arrays;
+
+import org.junit.Test;
+
+import adql.db.DBColumn;
+import adql.parser.ADQLParser;
+import adql.parser.ADQLParser.ADQLVersion;
+import adql.parser.grammar.ParseException;
+import adql.query.operand.ADQLColumn;
+
+public class TestWithItem {
+
+	@Test
+	public void testWithItemStringADQLQuery() {
+		// CASE: No label => ERROR!
+		String[] toTest = new String[]{ null, "", " 	 " };
+		for(String label : toTest) {
+			try {
+				new WithItem(label, null);
+				fail("It should be impossible to create a WithItem without a label!");
+			} catch(Exception ex) {
+				assertEquals(NullPointerException.class, ex.getClass());
+				assertEquals("Missing label of the WITH item!", ex.getMessage());
+			}
+		}
+
+		// CASE: No query => ERROR!
+		try {
+			new WithItem("query", null);
+			fail("It should be impossible to create a WithItem without a query!");
+		} catch(Exception ex) {
+			assertEquals(NullPointerException.class, ex.getClass());
+			assertEquals("Missing query of the WITH item!", ex.getMessage());
+		}
+
+		// CASE: label + query => OK!
+		final String WITH_LABEL = "aNamedQuery";
+		WithItem item = new WithItem(WITH_LABEL, new ADQLQuery());
+		assertEquals(WITH_LABEL, item.getLabel());
+		assertFalse(item.isLabelCaseSensitive());
+		assertNotNull(item.getQuery());
+		assertNull(item.getColumnLabels());
+
+		// CASE: label + query => OK!
+		item = new WithItem("\"" + WITH_LABEL + "\"", new ADQLQuery());
+		assertEquals(WITH_LABEL, item.getLabel());
+		assertTrue(item.isLabelCaseSensitive());
+		assertNotNull(item.getQuery());
+		assertNull(item.getColumnLabels());
+	}
+
+	@Test
+	public void testWithItemStringADQLQueryCollectionOfIdentifierItem() {
+
+		// CASE: No label => ERROR!
+		String[] toTest = new String[]{ null, "", " 	 " };
+		for(String label : toTest) {
+			try {
+				new WithItem(label, null, null);
+				fail("It should be impossible to create a WithItem without a label!");
+			} catch(Exception ex) {
+				assertEquals(NullPointerException.class, ex.getClass());
+				assertEquals("Missing label of the WITH item!", ex.getMessage());
+			}
+		}
+
+		// CASE: No query => ERROR!
+		try {
+			new WithItem("query", null, null);
+			fail("It should be impossible to create a WithItem without a query!");
+		} catch(Exception ex) {
+			assertEquals(NullPointerException.class, ex.getClass());
+			assertEquals("Missing query of the WITH item!", ex.getMessage());
+		}
+
+		// CASE: label + query but no col. label => OK!
+		final String WITH_LABEL = "aNamedQuery";
+		WithItem item = new WithItem(WITH_LABEL, new ADQLQuery(), null);
+		assertEquals(WITH_LABEL, item.getLabel());
+		assertNotNull(item.getQuery());
+		assertNull(item.getColumnLabels());
+
+		// CASE: label + query + col. labels => OK!
+		item = new WithItem(WITH_LABEL, new ADQLQuery(), Arrays.asList(new ADQLColumn[]{ new ADQLColumn("aColumn") }));
+		assertEquals(WITH_LABEL, item.getLabel());
+		assertNotNull(item.getQuery());
+		assertNotNull(item.getColumnLabels());
+		assertEquals(1, item.getColumnLabels().size());
+	}
+
+	@Test
+	public void testToADQL() {
+		try {
+			ADQLParser parser = new ADQLParser(ADQLVersion.V2_1);
+
+			// CASE: NO column labels
+			WithItem item = new WithItem("myQuery", parser.parseQuery("SELECT foo FROM bar"));
+			assertEquals("myQuery AS (\n" + "SELECT foo\n" + "FROM bar\n" + ")", item.toADQL());
+
+			// CASE: WITH column labels
+			item = new WithItem("myQuery", parser.parseQuery("SELECT foo, stuff FROM bar"), Arrays.asList(new ADQLColumn[]{ new ADQLColumn("aColumn"), new ADQLColumn("\"Another\"Column\"") }));
+			assertEquals("myQuery(aColumn,\"Another\"\"Column\") AS (\n" + "SELECT foo , stuff\n" + "FROM bar\n" + ")", item.toADQL());
+
+			// CASE: after an integral parsing
+			ADQLQuery query = parser.parseQuery("WITH myQuery(aColumn, \"Another\"\"Column\") AS (SELECT foo, stuff FROM bar) SELECT * FROM myQuery");
+			assertEquals("WITH myQuery(aColumn,\"Another\"\"Column\") AS (\n" + "SELECT foo , stuff\n" + "FROM bar\n" + ")\nSELECT *\nFROM myQuery", query.toADQL());
+		} catch(ParseException ex) {
+			ex.printStackTrace();
+			fail("Unexpected parsing error!");
+		}
+	}
+
+	@Test
+	public void testGetResultingColumns() {
+		try {
+			ADQLParser parser = new ADQLParser(ADQLVersion.V2_1);
+
+			// CASE: NO column labels
+			WithItem item = new WithItem("myQuery", parser.parseQuery("SELECT foo FROM bar"));
+			DBColumn[] lstCol = item.getResultingColumns();
+			assertNotNull(lstCol);
+			assertEquals(1, lstCol.length);
+			assertEquals("foo", lstCol[0].getADQLName());
+			assertEquals(lstCol[0].getADQLName(), lstCol[0].getDBName());
+			assertFalse(lstCol[0].isCaseSensitive());
+
+			// CASE: WITH column labels
+			item = new WithItem("myQuery", parser.parseQuery("SELECT foo, stuff FROM bar"), Arrays.asList(new ADQLColumn[]{ new ADQLColumn("aColumn"), new ADQLColumn("\"Another\"Column\"") }));
+			assertEquals("myQuery(aColumn,\"Another\"\"Column\") AS (\n" + "SELECT foo , stuff\n" + "FROM bar\n" + ")", item.toADQL());
+			lstCol = item.getResultingColumns();
+			assertNotNull(lstCol);
+			assertEquals(2, lstCol.length);
+			assertEquals("acolumn", lstCol[0].getADQLName());
+			assertEquals(lstCol[0].getADQLName(), lstCol[0].getDBName());
+			assertFalse(lstCol[0].isCaseSensitive());
+			assertEquals("Another\"Column", lstCol[1].getADQLName());
+			assertEquals(lstCol[1].getADQLName(), lstCol[1].getDBName());
+			assertTrue(lstCol[1].isCaseSensitive());
+		} catch(ParseException ex) {
+			ex.printStackTrace();
+			fail("Unexpected parsing error!");
+		}
+	}
+
+}
diff --git a/test/adql/translator/TestJDBCTranslator.java b/test/adql/translator/TestJDBCTranslator.java
index dbd3cbfc2b2269ffd1977762e61945fdf1948c0c..2f8a5a010d92917b9bc5cfc8e0e5595d8aea12fa 100644
--- a/test/adql/translator/TestJDBCTranslator.java
+++ b/test/adql/translator/TestJDBCTranslator.java
@@ -1,12 +1,21 @@
 package adql.translator;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+
 import org.junit.Before;
 import org.junit.Test;
 
+import adql.db.DBChecker;
+import adql.db.DBTable;
 import adql.db.DBType;
+import adql.db.DefaultDBColumn;
+import adql.db.DefaultDBTable;
 import adql.db.FunctionDef;
 import adql.db.STCS.Region;
 import adql.parser.ADQLParser;
@@ -15,7 +24,9 @@ import adql.parser.feature.FeatureSet;
 import adql.parser.feature.LanguageFeature;
 import adql.parser.grammar.ParseException;
 import adql.query.ADQLQuery;
+import adql.query.ClauseADQL;
 import adql.query.IdentifierField;
+import adql.query.WithItem;
 import adql.query.operand.ADQLColumn;
 import adql.query.operand.ADQLOperand;
 import adql.query.operand.NumericConstant;
@@ -42,6 +53,70 @@ public class TestJDBCTranslator {
 	public void setUp() throws Exception {
 	}
 
+	@Test
+	public void testTranslateWithClause() {
+		JDBCTranslator tr = new AJDBCTranslator();
+		ADQLParser parser = new ADQLParser(ADQLVersion.V2_1);
+
+		try {
+			// CASE: No WITH clause
+			ADQLQuery query = parser.parseQuery("SELECT * FROM foo");
+			ClauseADQL<WithItem> withClause = query.getWith();
+			assertTrue(withClause.isEmpty());
+			assertEquals("WITH ", tr.translate(withClause));
+			assertEquals("SELECT *\nFROM foo", tr.translate(query));
+
+			// CASE: A single WITH item
+			query = parser.parseQuery("WITH foo AS (SELECT * FROM bar) SELECT * FROM foo");
+			withClause = query.getWith();
+			assertEquals(1, withClause.size());
+			assertEquals("WITH \"foo\" AS (\nSELECT *\nFROM bar\n)", tr.translate(withClause));
+			assertEquals("WITH \"foo\" AS (\nSELECT *\nFROM bar\n)\nSELECT *\nFROM foo", tr.translate(query));
+
+			// CASE: Several WITH items
+			query = parser.parseQuery("WITH foo AS (SELECT * FROM bar), Foo2(myCol) AS (SELECT aCol FROM myTable) SELECT * FROM foo JOIN foo2 ON foo.id = foo2.myCol");
+			withClause = query.getWith();
+			assertEquals(2, withClause.size());
+			assertEquals("WITH \"foo\" AS (\nSELECT *\nFROM bar\n) , \"foo2\"(\"mycol\") AS (\nSELECT aCol AS \"aCol\"\nFROM myTable\n)", tr.translate(withClause));
+			assertEquals("WITH \"foo\" AS (\nSELECT *\nFROM bar\n) , \"foo2\"(\"mycol\") AS (\nSELECT aCol AS \"aCol\"\nFROM myTable\n)\nSELECT *\nFROM foo INNER JOIN foo2 ON foo.id = foo2.myCol", tr.translate(query));
+
+		} catch(ParseException pe) {
+			pe.printStackTrace();
+			fail("Unexpected parsing failure! (see console for more details)");
+		} catch(Exception ex) {
+			ex.printStackTrace();
+			fail("Unexpected error while translating a correct WITH item! (see console for more details)");
+		}
+	}
+
+	@Test
+	public void testTranslateWithItem() {
+		JDBCTranslator tr = new AJDBCTranslator();
+
+		try {
+			// CASE: Simple WITH item (no case sensitivity, no column labels)
+			WithItem item = new WithItem("Foo", (new ADQLParser(ADQLVersion.V2_1)).parseQuery("SELECT * FROM bar"));
+			item.setLabelCaseSensitive(false);
+			assertEquals("\"foo\" AS (\nSELECT *\nFROM bar\n)", tr.translate(item));
+
+			// CASE: WITH item with case sensitivity and column labels
+			item = new WithItem("Foo", (new ADQLParser(ADQLVersion.V2_1)).parseQuery("SELECT col1, col2 FROM bar"), Arrays.asList(new ADQLColumn[]{ new ADQLColumn("FirstColumn"), new ADQLColumn("\"SecondColumn\"") }));
+			item.setLabelCaseSensitive(true);
+			assertEquals("\"Foo\"(\"firstcolumn\",\"SecondColumn\") AS (\nSELECT col1 AS \"col1\" , col2 AS \"col2\"\nFROM bar\n)", tr.translate(item));
+
+			// CASE: query with an inner WITH
+			item = new WithItem("Foo", (new ADQLParser(ADQLVersion.V2_1)).parseQuery("WITH bar(col1, col2) AS (SELECT aCol, anotherCol FROM stuff) SELECT * FROM bar"));
+			assertEquals("\"foo\" AS (\nWITH \"bar\"(\"col1\",\"col2\") AS (\nSELECT aCol AS \"aCol\" , anotherCol AS \"anotherCol\"\nFROM stuff\n)\nSELECT *\nFROM bar\n)", tr.translate(item));
+
+		} catch(ParseException pe) {
+			pe.printStackTrace();
+			fail("Unexpected parsing failure! (see console for more details)");
+		} catch(Exception ex) {
+			ex.printStackTrace();
+			fail("Unexpected error while translating a correct WITH item! (see console for more details)");
+		}
+	}
+
 	public final static int countFeatures(final FeatureSet features) {
 		int cnt = 0;
 		for(LanguageFeature feat : features)
@@ -181,6 +256,80 @@ public class TestJDBCTranslator {
 		}
 	}
 
+	@Test
+	public void testNaturalJoin() {
+		ArrayList<DBTable> tables = new ArrayList<DBTable>(2);
+		DefaultDBTable t = new DefaultDBTable("aTable");
+		t.addColumn(new DefaultDBColumn("id", t));
+		t.addColumn(new DefaultDBColumn("name", t));
+		t.addColumn(new DefaultDBColumn("aColumn", t));
+		tables.add(t);
+		t = new DefaultDBTable("anotherTable");
+		t.addColumn(new DefaultDBColumn("id", t));
+		t.addColumn(new DefaultDBColumn("name", t));
+		t.addColumn(new DefaultDBColumn("anotherColumn", t));
+		tables.add(t);
+
+		final String adqlquery = "SELECT id, name, aColumn, anotherColumn FROM aTable A NATURAL JOIN anotherTable B;";
+
+		try {
+			ADQLParser parser = new ADQLParser();
+			parser.setQueryChecker(new DBChecker(tables));
+			ADQLQuery query = parser.parseQuery(adqlquery);
+			JDBCTranslator translator = new AJDBCTranslator();
+
+			// Test the FROM part:
+			assertEquals("aTable AS \"a\" NATURAL INNER JOIN anotherTable AS \"b\" ", translator.translate(query.getFrom()));
+
+			// Test the SELECT part (in order to ensure the usual common columns (due to NATURAL) are actually translated as columns of the first joined table):
+			assertEquals("SELECT id AS \"id\" , name AS \"name\" , a.aColumn AS \"acolumn\" , b.anotherColumn AS \"anothercolumn\"", translator.translate(query.getSelect()));
+
+		} catch(ParseException pe) {
+			pe.printStackTrace();
+			fail("The given ADQL query is completely correct. No error should have occurred while parsing it. (see the console for more details)");
+		} catch(TranslationException te) {
+			te.printStackTrace();
+			fail("No error was expected from this translation. (see the console for more details)");
+		}
+	}
+
+	@Test
+	public void testJoinWithUSING() {
+		ArrayList<DBTable> tables = new ArrayList<DBTable>(2);
+		DefaultDBTable t = new DefaultDBTable("aTable");
+		t.addColumn(new DefaultDBColumn("id", t));
+		t.addColumn(new DefaultDBColumn("name", t));
+		t.addColumn(new DefaultDBColumn("aColumn", t));
+		tables.add(t);
+		t = new DefaultDBTable("anotherTable");
+		t.addColumn(new DefaultDBColumn("id", t));
+		t.addColumn(new DefaultDBColumn("name", t));
+		t.addColumn(new DefaultDBColumn("anotherColumn", t));
+		tables.add(t);
+
+		final String adqlquery = "SELECT B.id, name, aColumn, anotherColumn FROM aTable A JOIN anotherTable B USING(name);";
+
+		try {
+			ADQLParser parser = new ADQLParser();
+			parser.setQueryChecker(new DBChecker(tables));
+			ADQLQuery query = parser.parseQuery(adqlquery);
+			JDBCTranslator translator = new AJDBCTranslator();
+
+			// Test the FROM part:
+			assertEquals("aTable AS \"a\" INNER JOIN anotherTable AS \"b\" USING (name)", translator.translate(query.getFrom()));
+
+			// Test the SELECT part (in order to ensure the usual common columns (due to USING) are actually translated as columns of the first joined table):
+			assertEquals("SELECT b.id AS \"id\" , name AS \"name\" , a.aColumn AS \"acolumn\" , b.anotherColumn AS \"anothercolumn\"", translator.translate(query.getSelect()));
+
+		} catch(ParseException pe) {
+			pe.printStackTrace();
+			fail("The given ADQL query is completely correct. No error should have occurred while parsing it. (see the console for more details)");
+		} catch(TranslationException te) {
+			te.printStackTrace();
+			fail("No error was expected from this translation. (see the console for more details)");
+		}
+	}
+
 	public final static class AJDBCTranslator extends JDBCTranslator {
 
 		@Override
diff --git a/test/adql/translator/TestSQLServerTranslator.java b/test/adql/translator/TestSQLServerTranslator.java
index 1629efb0df5dbee4fda337fefc543defd849f4da..89d34a11e00764416354e3c9939c7b1e88de92ce 100644
--- a/test/adql/translator/TestSQLServerTranslator.java
+++ b/test/adql/translator/TestSQLServerTranslator.java
@@ -88,7 +88,7 @@ public class TestSQLServerTranslator {
 			assertEquals("\"aTable\" AS \"a\" INNER JOIN \"anotherTable\" AS \"b\" ON \"a\".\"id\"=\"b\".\"id\" AND \"a\".\"name\"=\"b\".\"name\"", translator.translate(query.getFrom()));
 
 			// Test the SELECT part (in order to ensure the usual common columns (due to NATURAL) are actually translated as columns of the first joined table):
-			assertEquals("SELECT \"a\".\"id\" AS \"id\" , \"a\".\"name\" AS \"name\" , \"a\".\"aColumn\" AS \"aColumn\" , \"b\".\"anotherColumn\" AS \"anotherColumn\"", translator.translate(query.getSelect()));
+			assertEquals("SELECT \"a\".\"id\" AS \"id\" , \"a\".\"name\" AS \"name\" , \"a\".\"aColumn\" AS \"acolumn\" , \"b\".\"anotherColumn\" AS \"anothercolumn\"", translator.translate(query.getSelect()));
 
 		} catch(ParseException pe) {
 			pe.printStackTrace();
@@ -114,7 +114,7 @@ public class TestSQLServerTranslator {
 			assertEquals("\"aTable\" AS \"a\" INNER JOIN \"anotherTable\" AS \"b\" ON \"a\".\"name\"=\"b\".\"name\"", translator.translate(query.getFrom()));
 
 			// Test the SELECT part (in order to ensure the usual common columns (due to USING) are actually translated as columns of the first joined table):
-			assertEquals("SELECT \"b\".\"id\" AS \"id\" , \"a\".\"name\" AS \"name\" , \"a\".\"aColumn\" AS \"aColumn\" , \"b\".\"anotherColumn\" AS \"anotherColumn\"", translator.translate(query.getSelect()));
+			assertEquals("SELECT \"b\".\"id\" AS \"id\" , \"a\".\"name\" AS \"name\" , \"a\".\"aColumn\" AS \"acolumn\" , \"b\".\"anotherColumn\" AS \"anothercolumn\"", translator.translate(query.getSelect()));
 
 		} catch(ParseException pe) {
 			pe.printStackTrace();
diff --git a/test/tap/config/TestTAPConfiguration.java b/test/tap/config/TestTAPConfiguration.java
index 30d8092d5e4decb7498b6312326ad5c03f5a7a8b..602e754d0288603b2849a6f70e04b3f6ff34d3bd 100644
--- a/test/tap/config/TestTAPConfiguration.java
+++ b/test/tap/config/TestTAPConfiguration.java
@@ -26,28 +26,29 @@ import java.util.Properties;
 import org.junit.Before;
 import org.junit.Test;
 
+import adql.query.ColumnReference;
 import tap.ServiceConnection;
 import tap.ServiceConnection.LimitUnit;
 import tap.TAPException;
 import tap.TAPFactory;
 import tap.metadata.TAPMetadata;
 import tap.metadata.TAPSchema;
-import adql.query.ColumnReference;
 
 public class TestTAPConfiguration {
 
 	@Before
-	public void setUp() throws Exception{}
+	public void setUp() throws Exception {
+	}
 
 	/**
 	 * TEST isClassName(String):
 	 * 	- null, "", "{}", "an incorrect syntax" 				=> FALSE must be returned
 	 * 	- "{ }", "{ 	}", "{class.path}", "{ class.path	}" 	=> TRUE must be returned
-	 * 
+	 *
 	 * @see ConfigurableServiceConnection#isClassName(String)
 	 */
 	@Test
-	public void testIsClassPath(){
+	public void testIsClassPath() {
 		// NULL and EMPTY:
 		assertFalse(isClassName(null));
 		assertFalse(isClassName(""));
@@ -76,79 +77,79 @@ public class TestTAPConfiguration {
 	 * 	- "{mypackage.foo}", "{java.util.ArrayList}" (while a String is expected)	=> a TAPException must be thrown
 	 */
 	@Test
-	public void testGetClassStringStringClass(){
+	public void testGetClassStringStringClass() {
 		// NULL and EMPTY:
-		try{
+		try {
 			assertNull(fetchClass(null, KEY_FILE_MANAGER, String.class));
-		}catch(TAPException e){
+		} catch(TAPException e) {
 			fail("If a NULL value is provided as class name: getClass(...) MUST return null!\nCaught exception: " + getPertinentMessage(e));
 		}
-		try{
+		try {
 			assertNull(fetchClass("", KEY_FILE_MANAGER, String.class));
-		}catch(TAPException e){
+		} catch(TAPException e) {
 			fail("If an EMPTY value is provided as class name: getClass(...) MUST return null!\nCaught exception: " + getPertinentMessage(e));
 		}
 
 		// EMPTY CLASS NAME:
-		try{
+		try {
 			assertNull(fetchClass("{}", KEY_FILE_MANAGER, String.class));
-		}catch(TAPException e){
+		} catch(TAPException e) {
 			fail("If an EMPTY class name is provided: getClass(...) MUST return null!\nCaught exception: " + getPertinentMessage(e));
 		}
 
 		// INCORRECT SYNTAX:
-		try{
+		try {
 			assertNull(fetchClass("incorrect class name ; missing {}", KEY_FILE_MANAGER, String.class));
-		}catch(TAPException e){
+		} catch(TAPException e) {
 			fail("If an incorrect class name is provided: getClass(...) MUST return null!\nCaught exception: " + getPertinentMessage(e));
 		}
 
 		// VALID CLASS NAME:
-		try{
+		try {
 			Class<? extends String> classObject = fetchClass("{java.lang.String}", KEY_FILE_MANAGER, String.class);
 			assertNotNull(classObject);
 			assertEquals(classObject.getName(), "java.lang.String");
-		}catch(TAPException e){
+		} catch(TAPException e) {
 			fail("If a VALID class name is provided: getClass(...) MUST return a Class object of the wanted type!\nCaught exception: " + getPertinentMessage(e));
 		}
 
 		// INCORRECT CLASS NAME:
-		try{
+		try {
 			fetchClass("{mypackage.foo}", KEY_FILE_MANAGER, String.class);
 			fail("This MUST have failed because an incorrect class name is provided!");
-		}catch(TAPException e){
+		} catch(TAPException e) {
 			assertEquals(e.getClass(), TAPException.class);
 			assertEquals(e.getMessage(), "The class specified by the property \"" + KEY_FILE_MANAGER + "\" ({mypackage.foo}) can not be found.");
 		}
 
 		// INCOMPATIBLE TYPES:
-		try{
+		try {
 			@SuppressWarnings("unused")
 			Class<? extends String> classObject = fetchClass("{java.util.ArrayList}", KEY_FILE_MANAGER, String.class);
 			fail("This MUST have failed because a class of a different type has been asked!");
-		}catch(TAPException e){
+		} catch(TAPException e) {
 			assertEquals(e.getClass(), TAPException.class);
 			assertEquals(e.getMessage(), "The class specified by the property \"" + KEY_FILE_MANAGER + "\" ({java.util.ArrayList}) is not implementing " + String.class.getName() + ".");
 		}
 
 		// CLASS NAME VALID ONLY IN THE SYNTAX:
-		try{
+		try {
 			assertNull(fetchClass("{ }", KEY_FILE_MANAGER, String.class));
-		}catch(TAPException e){
+		} catch(TAPException e) {
 			fail("If an EMPTY class name is provided: getClass(...) MUST return null!\nCaught exception: " + getPertinentMessage(e));
 		}
-		try{
+		try {
 			assertNull(fetchClass("{		}", KEY_FILE_MANAGER, String.class));
-		}catch(TAPException e){
+		} catch(TAPException e) {
 			fail("If an EMPTY class name is provided: getClass(...) MUST return null!\nCaught exception: " + getPertinentMessage(e));
 		}
 
 		// NOT TRIM CLASS NAME:
-		try{
+		try {
 			Class<?> classObject = fetchClass("{ java.lang.String	}", KEY_FILE_MANAGER, String.class);
 			assertNotNull(classObject);
 			assertEquals(classObject.getName(), "java.lang.String");
-		}catch(TAPException e){
+		} catch(TAPException e) {
 			fail("If a VALID class name is provided: getClass(...) MUST return a Class object of the wanted type!\nCaught exception: " + getPertinentMessage(e));
 		}
 	}
@@ -163,82 +164,82 @@ public class TestTAPConfiguration {
 	 * 	- if the specified constructor exists return <code>true</code>, else <code>false</code> must be returned.
 	 */
 	@Test
-	public void testHasConstructor(){
+	public void testHasConstructor() {
 		/* hasConstructor(...) must throw an exception if the specification of the class (1st and 3rd parameters)
 		 * is wrong. But that is performed by fetchClass(...) which is called at the beginning of the function
 		 * and is not surrounded by a try-catch. So all these tests are already done by testGetClassStringStringClass(). */
 
 		// With a missing list of parameters:
-		try{
+		try {
 			assertTrue(hasConstructor("{java.lang.String}", "STRING", String.class, null));
-		}catch(TAPException te){
+		} catch(TAPException te) {
 			te.printStackTrace();
 			fail("\"No list of parameters\" MUST be interpreted as the specification of a constructor with no parameter! This test has failed.");
 		}
 
 		// With an empty list of parameters
-		try{
+		try {
 			assertTrue(hasConstructor("{java.lang.String}", "STRING", String.class, new Class[0]));
-		}catch(TAPException te){
+		} catch(TAPException te) {
 			te.printStackTrace();
 			fail("\"An empty list of parameters\" MUST be interpreted as the specification of a constructor with no parameter! This test has failed.");
 		}
 
 		// With a wrong list of parameters - 1
-		try{
+		try {
 			assertFalse(hasConstructor("{tap.config.ConfigurableTAPFactory}", KEY_TAP_FACTORY, TAPFactory.class, new Class[]{}));
-		}catch(TAPException te){
+		} catch(TAPException te) {
 			te.printStackTrace();
 			fail("ConfigurableTAPFactory does not have an empty constructor ; this test should have failed!");
 		}
 
 		// With a wrong list of parameters - 2
-		try{
-			assertFalse(hasConstructor("{tap.config.ConfigurableTAPFactory}", KEY_TAP_FACTORY, TAPFactory.class, new Class[]{String.class,String.class}));
-		}catch(TAPException te){
+		try {
+			assertFalse(hasConstructor("{tap.config.ConfigurableTAPFactory}", KEY_TAP_FACTORY, TAPFactory.class, new Class[]{ String.class, String.class }));
+		} catch(TAPException te) {
 			te.printStackTrace();
 			fail("ConfigurableTAPFactory does not have a constructor with 2 Strings as parameter ; this test should have failed!");
 		}
 
 		// With a good list of parameters - 1
-		try{
-			assertTrue(hasConstructor("{tap.config.ConfigurableTAPFactory}", KEY_TAP_FACTORY, TAPFactory.class, new Class[]{ServiceConnection.class,Properties.class}));
-		}catch(TAPException te){
+		try {
+			assertTrue(hasConstructor("{tap.config.ConfigurableTAPFactory}", KEY_TAP_FACTORY, TAPFactory.class, new Class[]{ ServiceConnection.class, Properties.class }));
+		} catch(TAPException te) {
 			te.printStackTrace();
 			fail("ConfigurableTAPFactory has a constructor with a ServiceConnection and a Properties in parameters ; this test should have failed!");
 		}
 
 		// With a good list of parameters - 2
-		try{
-			assertTrue(hasConstructor("{java.lang.String}", "STRING", String.class, new Class[]{String.class}));
-		}catch(TAPException te){
+		try {
+			assertTrue(hasConstructor("{java.lang.String}", "STRING", String.class, new Class[]{ String.class }));
+		} catch(TAPException te) {
 			te.printStackTrace();
 			fail("String has a constructor with a String as parameter ; this test should have failed!");
 		}
 	}
 
 	@Test
-	public void testNewInstance(){
+	public void testNewInstance() {
 		// VALID CONSTRUCTOR with no parameters:
-		try{
+		try {
 			TAPMetadata metadata = newInstance("{tap.metadata.TAPMetadata}", "metadata", TAPMetadata.class);
 			assertNotNull(metadata);
 			assertEquals("tap.metadata.TAPMetadata", metadata.getClass().getName());
-		}catch(Exception ex){
+		} catch(Exception ex) {
 			ex.printStackTrace();
 			fail("This test should have succeeded: the parameters of newInstance(...) are all valid.");
 		}
 
 		// VALID CONSTRUCTOR with some parameters:
-		try{
+		try {
 			final String schemaName = "MySuperSchema", description = "And its less super description.", utype = "UTYPE";
-			TAPSchema schema = newInstance("{tap.metadata.TAPSchema}", "schema", TAPSchema.class, new Class<?>[]{String.class,String.class,String.class}, new String[]{schemaName,description,utype});
+			TAPSchema schema = newInstance("{tap.metadata.TAPSchema}", "schema", TAPSchema.class, new Class<?>[]{ String.class, String.class, String.class }, new String[]{ schemaName, description, utype });
 			assertNotNull(schema);
 			assertEquals("tap.metadata.TAPSchema", schema.getClass().getName());
 			assertEquals(schemaName, schema.getADQLName());
 			assertEquals(description, schema.getDescription());
 			assertEquals(utype, schema.getUtype());
-		}catch(Exception ex){
+		} catch(Exception ex) {
 			ex.printStackTrace();
 			fail("This test should have succeeded: the constructor TAPSchema(String,String,String) exists.");
 		}
@@ -246,79 +247,80 @@ public class TestTAPConfiguration {
 		// VALID CONSTRUCTOR with some parameters whose the type is an extension (not the exact type):
 		OutputStream output = null;
 		File tmp = new File("tmp.empty");
-		try{
-			output = newInstance("{java.io.BufferedOutputStream}", "stream", OutputStream.class, new Class<?>[]{OutputStream.class}, new OutputStream[]{new FileOutputStream(tmp)});
+		try {
+			output = newInstance("{java.io.BufferedOutputStream}", "stream", OutputStream.class, new Class<?>[]{ OutputStream.class }, new OutputStream[]{ new FileOutputStream(tmp) });
 			assertNotNull(output);
 			assertEquals(BufferedOutputStream.class, output.getClass());
-		}catch(Exception ex){
+		} catch(Exception ex) {
 			ex.printStackTrace();
 			fail("This test should have succeeded: the constructor TAPSchema(String,String,String) exists.");
-		}finally{
-			try{
+		} finally {
+			try {
 				tmp.delete();
 				if (output != null)
 					output.close();
-			}catch(IOException ioe){}
+			} catch(IOException ioe) {
+			}
 		}
 
 		// NOT A CLASS NAME:
-		try{
+		try {
 			newInstance("tap.metadata.TAPMetadata", "metadata", TAPMetadata.class);
 			fail("This MUST have failed because the property value is not a class name!");
-		}catch(Exception ex){
+		} catch(Exception ex) {
 			assertEquals(TAPException.class, ex.getClass());
 			assertEquals("Class name expected for the property \"metadata\" instead of: \"tap.metadata.TAPMetadata\"! The specified class must extend/implement tap.metadata.TAPMetadata.", ex.getMessage());
 		}
 
 		// NO MATCHING CONSTRUCTOR:
-		try{
-			newInstance("{tap.metadata.TAPSchema}", "schema", TAPSchema.class, new Class<?>[]{Integer.class}, new Object[]{new Integer(123)});
+		try {
+			newInstance("{tap.metadata.TAPSchema}", "schema", TAPSchema.class, new Class<?>[]{ Integer.class }, new Object[]{ new Integer(123) });
 			fail("This MUST have failed because the specified class does not have any expected constructor!");
-		}catch(Exception ex){
+		} catch(Exception ex) {
 			assertEquals(TAPException.class, ex.getClass());
 			assertEquals("Missing constructor tap.metadata.TAPSchema(java.lang.Integer)! See the value \"{tap.metadata.TAPSchema}\" of the property \"schema\".", ex.getMessage());
 		}
 
 		// VALID CONSTRUCTOR with primitive type:
-		try{
-			ColumnReference colRef = newInstance("{adql.query.ColumnReference}", "colRef", ColumnReference.class, new Class<?>[]{int.class}, new Object[]{123});
+		try {
+			ColumnReference colRef = newInstance("{adql.query.ColumnReference}", "colRef", ColumnReference.class, new Class<?>[]{ int.class }, new Object[]{ 123 });
 			assertNotNull(colRef);
 			assertEquals(ColumnReference.class, colRef.getClass());
 			assertEquals(123, colRef.getColumnIndex());
-			colRef = newInstance("{adql.query.ColumnReference}", "colRef", ColumnReference.class, new Class<?>[]{int.class}, new Object[]{new Integer(123)});
+			colRef = newInstance("{adql.query.ColumnReference}", "colRef", ColumnReference.class, new Class<?>[]{ int.class }, new Object[]{ new Integer(123) });
 			assertNotNull(colRef);
 			assertEquals(ColumnReference.class, colRef.getClass());
 			assertEquals(123, colRef.getColumnIndex());
-		}catch(Exception ex){
+		} catch(Exception ex) {
 			ex.printStackTrace();
 			fail("This test should have succeeded: the constructor ColumnReference(int) exists.");
 		}
 
 		// WRONG CONSTRUCTOR with primitive type:
-		try{
-			newInstance("{adql.query.ColumnReference}", "colRef", ColumnReference.class, new Class<?>[]{Integer.class}, new Object[]{new Integer(123)});
+		try {
+			newInstance("{adql.query.ColumnReference}", "colRef", ColumnReference.class, new Class<?>[]{ Integer.class }, new Object[]{ new Integer(123) });
 			fail("This MUST have failed because the constructor of the specified class expects an int, not an java.lang.Integer!");
-		}catch(Exception ex){
+		} catch(Exception ex) {
 			assertEquals(TAPException.class, ex.getClass());
 			assertEquals("Missing constructor adql.query.ColumnReference(java.lang.Integer)! See the value \"{adql.query.ColumnReference}\" of the property \"colRef\".", ex.getMessage());
 		}
 
 		// THE CONSTRUCTOR THROWS AN EXCEPTION:
-		try{
-			newInstance("{tap.metadata.TAPSchema}", "schema", TAPSchema.class, new Class<?>[]{String.class}, new Object[]{null});
+		try {
+			newInstance("{tap.metadata.TAPSchema}", "schema", TAPSchema.class, new Class<?>[]{ String.class }, new Object[]{ null });
 			fail("This MUST have failed because the constructor of the specified class throws an exception!");
-		}catch(Exception ex){
+		} catch(Exception ex) {
 			assertEquals(TAPException.class, ex.getClass());
 			assertNotNull(ex.getCause());
 			assertEquals(NullPointerException.class, ex.getCause().getClass());
-			assertEquals("Missing schema name!", ex.getCause().getMessage());
+			assertEquals("Missing ADQL name!", ex.getCause().getMessage());
 		}
 
 		// THE CONSTRUCTOR THROWS A TAPEXCEPTION:
-		try{
+		try {
 			newInstance("{tap.config.TestTAPConfiguration$ClassAlwaysThrowTAPError}", "tapError", ClassAlwaysThrowTAPError.class);
 			fail("This MUST have failed because the constructor of the specified class throws a TAPException!");
-		}catch(Exception ex){
+		} catch(Exception ex) {
 			assertEquals(TAPException.class, ex.getClass());
 			assertEquals("This error is always thrown by ClassAlwaysThrowTAPError ^^", ex.getMessage());
 		}
@@ -337,13 +339,13 @@ public class TestTAPConfiguration {
 	 * 	- foo, 100b, 100TB, 1foo	=> an exception must occur
 	 */
 	@Test
-	public void testParseLimitStringString(){
+	public void testParseLimitStringString() {
 		final String propertyName = KEY_DEFAULT_OUTPUT_LIMIT + " or " + KEY_MAX_OUTPUT_LIMIT;
 		// Test empty or negative or null values => OK!
-		try{
-			String[] testValues = new String[]{null,"","  	 ","-123"};
+		try {
+			String[] testValues = new String[]{ null, "", "  	 ", "-123" };
 			Object[] limit;
-			for(String v : testValues){
+			for(String v : testValues) {
 				limit = parseLimit(v, propertyName, false);
 				assertEquals(limit[0], -1);
 				assertEquals(limit[1], LimitUnit.rows);
@@ -352,132 +354,132 @@ public class TestTAPConfiguration {
 			limit = parseLimit("0", propertyName, false);
 			assertEquals(limit[0], 0);
 			assertEquals(limit[1], LimitUnit.rows);
-		}catch(TAPException te){
+		} catch(TAPException te) {
 			fail("All these empty limit values are valid, so these tests should have succeeded!\nCaught exception: " + getPertinentMessage(te));
 		}
 
 		// Test all accepted rows values:
-		try{
-			String[] testValues = new String[]{"20","20r","20 R"};
+		try {
+			String[] testValues = new String[]{ "20", "20r", "20 R" };
 			Object[] limit;
-			for(String v : testValues){
+			for(String v : testValues) {
 				limit = parseLimit(v, propertyName, false);
 				assertEquals(limit[0], 20);
 				assertEquals(limit[1], LimitUnit.rows);
 			}
-		}catch(TAPException te){
+		} catch(TAPException te) {
 			fail("All these rows limit values are valid, so these tests should have succeeded!\nCaught exception: " + getPertinentMessage(te));
 		}
 
 		// Test all accepted bytes values:
-		try{
-			String[] testValues = new String[]{"100B","100 B"};
+		try {
+			String[] testValues = new String[]{ "100B", "100 B" };
 			Object[] limit;
-			for(String v : testValues){
+			for(String v : testValues) {
 				limit = parseLimit(v, propertyName, true);
 				assertEquals(limit[0], 100);
 				assertEquals(limit[1], LimitUnit.bytes);
 			}
-		}catch(TAPException te){
+		} catch(TAPException te) {
 			fail("All these bytes limit values are valid, so these tests should have succeeded!\nCaught exception: " + getPertinentMessage(te));
 		}
 
 		// Test all accepted kilo-bytes values:
-		try{
-			String[] testValues = new String[]{"100kB","100 k B"};
+		try {
+			String[] testValues = new String[]{ "100kB", "100 k B" };
 			Object[] limit;
-			for(String v : testValues){
+			for(String v : testValues) {
 				limit = parseLimit(v, propertyName, true);
 				assertEquals(limit[0], 100);
 				assertEquals(limit[1], LimitUnit.kilobytes);
 			}
-		}catch(TAPException te){
+		} catch(TAPException te) {
 			fail("All these kilo-bytes limit values are valid, so these tests should have succeeded!\nCaught exception: " + getPertinentMessage(te));
 		}
 
 		// Test all accepted mega-bytes values:
-		try{
-			String[] testValues = new String[]{"100MB","1 0 0MB"};
+		try {
+			String[] testValues = new String[]{ "100MB", "1 0 0MB" };
 			Object[] limit;
-			for(String v : testValues){
+			for(String v : testValues) {
 				limit = parseLimit(v, propertyName, true);
 				assertEquals(limit[0], 100);
 				assertEquals(limit[1], LimitUnit.megabytes);
 			}
-		}catch(TAPException te){
+		} catch(TAPException te) {
 			fail("All these mega-bytes limit values are valid, so these tests should have succeeded!\nCaught exception: " + getPertinentMessage(te));
 		}
 
 		// Test all accepted giga-bytes values:
-		try{
-			String[] testValues = new String[]{"100GB","1 0 0 G B"};
+		try {
+			String[] testValues = new String[]{ "100GB", "1 0 0 G B" };
 			Object[] limit;
-			for(String v : testValues){
+			for(String v : testValues) {
 				limit = parseLimit(v, propertyName, true);
 				assertEquals(limit[0], 100);
 				assertEquals(limit[1], LimitUnit.gigabytes);
 			}
-		}catch(TAPException te){
+		} catch(TAPException te) {
 			fail("All these giga-bytes limit values are valid, so these tests should have succeeded!\nCaught exception: " + getPertinentMessage(te));
 		}
 
 		// Test with only the ROWS unit provided:
-		try{
+		try {
 			Object[] limit = parseLimit("r", propertyName, false);
 			assertEquals(limit[0], -1);
 			assertEquals(limit[1], LimitUnit.rows);
-		}catch(TAPException te){
+		} catch(TAPException te) {
 			fail("Providing only the ROWS unit is valid, so this test should have succeeded!\nCaught exception: " + getPertinentMessage(te));
 		}
 
 		// Test with only the BYTES unit provided:
-		try{
+		try {
 			Object[] limit = parseLimit("kB", propertyName, true);
 			assertEquals(limit[0], -1);
 			assertEquals(limit[1], LimitUnit.kilobytes);
-		}catch(TAPException te){
+		} catch(TAPException te) {
 			fail("Providing only the BYTES unit is valid, so this test should have succeeded!\nCaught exception: " + getPertinentMessage(te));
 		}
 
 		// Test with incorrect limit formats:
-		String[] values = new String[]{"","100","100","1"};
-		String[] unitPart = new String[]{"foo","b","TB","foo"};
-		for(int i = 0; i < values.length; i++){
-			try{
+		String[] values = new String[]{ "", "100", "100", "1" };
+		String[] unitPart = new String[]{ "foo", "b", "TB", "foo" };
+		for(int i = 0; i < values.length; i++) {
+			try {
 				parseLimit(values[i] + unitPart[i], propertyName, true);
 				fail("This test should have failed because an incorrect limit is provided: \"" + values[i] + unitPart[i] + "\"!");
-			}catch(TAPException te){
+			} catch(TAPException te) {
 				assertEquals(te.getClass(), TAPException.class);
 				assertEquals(te.getMessage(), "Unknown limit unit (" + unitPart[i] + ") for the property " + propertyName + ": \"" + values[i] + unitPart[i] + "\"!");
 
 			}
 		}
 		// Test with an incorrect numeric limit value:
-		try{
+		try {
 			parseLimit("abc100b", propertyName, true);
 			fail("This test should have failed because an incorrect limit is provided: \"abc100b\"!");
-		}catch(TAPException te){
+		} catch(TAPException te) {
 			assertEquals(te.getClass(), TAPException.class);
 			assertEquals(te.getMessage(), "Integer expected for the property " + propertyName + " for the substring \"abc100\" of the whole value: \"abc100b\"!");
 		}
 
 		// Test with a BYTES unit whereas the BYTES unit is forbidden:
-		try{
+		try {
 			parseLimit("100B", propertyName, false);
 			fail("This test should have failed because an incorrect limit is provided: \"100B\"!");
-		}catch(TAPException te){
+		} catch(TAPException te) {
 			assertEquals(te.getClass(), TAPException.class);
 			assertEquals(te.getMessage(), "BYTES unit is not allowed for the property " + propertyName + " (100B)!");
 		}
 	}
 
-	public static final String getPertinentMessage(final Exception ex){
+	public static final String getPertinentMessage(final Exception ex) {
 		return (ex.getCause() == null || ex.getMessage().equals(ex.getCause().getMessage())) ? ex.getMessage() : ex.getCause().getMessage();
 	}
 
 	private static class ClassAlwaysThrowTAPError {
 		@SuppressWarnings("unused")
-		public ClassAlwaysThrowTAPError() throws TAPException{
+		public ClassAlwaysThrowTAPError() throws TAPException {
 			throw new TAPException("This error is always thrown by ClassAlwaysThrowTAPError ^^");
 		}
 	}
diff --git a/test/tap/metadata/TestMetadataNames.java b/test/tap/metadata/TestMetadataNames.java
index 4fdb9c9e98555cba22cee1f9c0b8f94b9d10f7cc..1480b222eae66fc96ef94a805b4b3a1cb9df9b82 100644
--- a/test/tap/metadata/TestMetadataNames.java
+++ b/test/tap/metadata/TestMetadataNames.java
@@ -27,7 +27,7 @@ public class TestMetadataNames {
 			new TAPSchema(null);
 			fail("It should be impossible to create a TAPSchema with a NULL name.");
 		} catch(NullPointerException npe) {
-			assertEquals("Missing schema name!", npe.getMessage());
+			assertEquals("Missing ADQL name!", npe.getMessage());
 		}
 
 		// Empty string (not a single character):
@@ -35,7 +35,7 @@ public class TestMetadataNames {
 			new TAPSchema("");
 			fail("It should be impossible to create a TAPSchema with an empty name.");
 		} catch(NullPointerException npe) {
-			assertEquals("Missing schema name!", npe.getMessage());
+			assertEquals("Missing ADQL name!", npe.getMessage());
 		}
 
 		// String with only space characters:
@@ -43,7 +43,7 @@ public class TestMetadataNames {
 			new TAPSchema(" 	");
 			fail("It should be impossible to create a TAPSchema with a name just composed of space characters.");
 		} catch(NullPointerException npe) {
-			assertEquals("Missing schema name!", npe.getMessage());
+			assertEquals("Missing ADQL name!", npe.getMessage());
 		}
 
 		// Empty quoted string I:
@@ -51,7 +51,7 @@ public class TestMetadataNames {
 			new TAPSchema("\"\"");
 			fail("It should be impossible to create a TAPSchema with a empty name even if quoted.");
 		} catch(NullPointerException npe) {
-			assertEquals("Missing schema name!", npe.getMessage());
+			assertEquals("Missing ADQL name!", npe.getMessage());
 		}
 
 		// Empty quoted string II:
@@ -59,7 +59,7 @@ public class TestMetadataNames {
 			new TAPSchema("\" \"");
 			fail("It should be impossible to create a TAPSchema with a empty name even if quoted.");
 		} catch(NullPointerException npe) {
-			assertEquals("Missing schema name!", npe.getMessage());
+			assertEquals("Missing ADQL name!", npe.getMessage());
 		}
 
 		// Non quoted names => ADQL_NAME = RAW_NAME = TRIMMED(GIVEN_NAME)
@@ -125,7 +125,7 @@ public class TestMetadataNames {
 			new TAPTable(null);
 			fail("It should be impossible to create a TAPTable with a NULL name.");
 		} catch(NullPointerException npe) {
-			assertEquals("Missing table name!", npe.getMessage());
+			assertEquals("Missing ADQL name!", npe.getMessage());
 		}
 
 		// Empty string (not a single character):
@@ -133,7 +133,7 @@ public class TestMetadataNames {
 			new TAPTable("");
 			fail("It should be impossible to create a TAPTable with an empty name.");
 		} catch(NullPointerException npe) {
-			assertEquals("Missing table name!", npe.getMessage());
+			assertEquals("Missing ADQL name!", npe.getMessage());
 		}
 
 		// String with only space characters:
@@ -141,7 +141,7 @@ public class TestMetadataNames {
 			new TAPTable(" 	");
 			fail("It should be impossible to create a TAPTable with a name just composed of space characters.");
 		} catch(NullPointerException npe) {
-			assertEquals("Missing table name!", npe.getMessage());
+			assertEquals("Missing ADQL name!", npe.getMessage());
 		}
 
 		// Empty quoted string I:
@@ -149,7 +149,7 @@ public class TestMetadataNames {
 			new TAPTable("\"\"");
 			fail("It should be impossible to create a TAPTable with a empty name even if quoted.");
 		} catch(NullPointerException npe) {
-			assertEquals("Missing table name!", npe.getMessage());
+			assertEquals("Missing ADQL name!", npe.getMessage());
 		}
 
 		// Empty quoted string II:
@@ -157,7 +157,7 @@ public class TestMetadataNames {
 			new TAPTable("\" \"");
 			fail("It should be impossible to create a TAPTable with a empty name even if quoted.");
 		} catch(NullPointerException npe) {
-			assertEquals("Missing table name!", npe.getMessage());
+			assertEquals("Missing ADQL name!", npe.getMessage());
 		}
 
 		// Non quoted names => ADQL_NAME = RAW_NAME = TRIMMED(GIVEN_NAME)
@@ -263,7 +263,7 @@ public class TestMetadataNames {
 			new TAPColumn(null);
 			fail("It should be impossible to create a TAPColumn with a NULL name.");
 		} catch(NullPointerException npe) {
-			assertEquals("Missing column name!", npe.getMessage());
+			assertEquals("Missing ADQL name!", npe.getMessage());
 		}
 
 		// Empty string (not a single character):
@@ -271,7 +271,7 @@ public class TestMetadataNames {
 			new TAPColumn("");
 			fail("It should be impossible to create a TAPColumn with an empty name.");
 		} catch(NullPointerException npe) {
-			assertEquals("Missing column name!", npe.getMessage());
+			assertEquals("Missing ADQL name!", npe.getMessage());
 		}
 
 		// String with only space characters:
@@ -279,7 +279,7 @@ public class TestMetadataNames {
 			new TAPColumn(" 	");
 			fail("It should be impossible to create a TAPColumn with a name just composed of space characters.");
 		} catch(NullPointerException npe) {
-			assertEquals("Missing column name!", npe.getMessage());
+			assertEquals("Missing ADQL name!", npe.getMessage());
 		}
 
 		// Empty quoted string I:
@@ -287,7 +287,7 @@ public class TestMetadataNames {
 			new TAPColumn("\"\"");
 			fail("It should be impossible to create a TAPColumn with a empty name even if quoted.");
 		} catch(NullPointerException npe) {
-			assertEquals("Missing column name!", npe.getMessage());
+			assertEquals("Missing ADQL name!", npe.getMessage());
 		}
 
 		// Empty quoted string II:
@@ -295,7 +295,7 @@ public class TestMetadataNames {
 			new TAPColumn("\" \"");
 			fail("It should be impossible to create a TAPColumn with a empty name even if quoted.");
 		} catch(NullPointerException npe) {
-			assertEquals("Missing column name!", npe.getMessage());
+			assertEquals("Missing ADQL name!", npe.getMessage());
 		}
 
 		// Non quoted names => ADQL_NAME = RAW_NAME = TRIMMED(GIVEN_NAME)