diff --git a/src/adql/db/DBChecker.java b/src/adql/db/DBChecker.java
index 90841974a4f220bb4070c6ce71cbdce06d1e77ac..26de9e19f4139f350bce3a8bb8be49524f0693d0 100644
--- a/src/adql/db/DBChecker.java
+++ b/src/adql/db/DBChecker.java
@@ -16,7 +16,8 @@ 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 2011 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ * Copyright 2011,2013-2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomishes Rechen Institute (ARI)
  */
 
 import java.util.ArrayList;
@@ -26,10 +27,8 @@ import java.util.HashMap;
 import adql.db.exception.UnresolvedColumnException;
 import adql.db.exception.UnresolvedIdentifiersException;
 import adql.db.exception.UnresolvedTableException;
-
 import adql.parser.ParseException;
 import adql.parser.QueryChecker;
-
 import adql.query.ADQLObject;
 import adql.query.ADQLQuery;
 import adql.query.ClauseSelect;
@@ -38,9 +37,7 @@ import adql.query.IdentifierField;
 import adql.query.SelectAllColumns;
 import adql.query.SelectItem;
 import adql.query.from.ADQLTable;
-
 import adql.query.operand.ADQLColumn;
-
 import adql.search.ISearchHandler;
 import adql.search.SearchColumnHandler;
 import adql.search.SimpleSearchHandler;
@@ -62,8 +59,8 @@ import adql.search.SimpleSearchHandler;
  * 	can be replaced in SQL by their DB name, if different. This mapping is done automatically by {@link adql.translator.PostgreSQLTranslator}.
  * </i></p>
  * 
- * @author Gr&eacute;gory Mantelet (CDS)
- * @version 08/2011
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 1.1 (11/2013)
  */
 public class DBChecker implements QueryChecker {
 
@@ -162,6 +159,7 @@ public class DBChecker implements QueryChecker {
 	 * @see #resolveColumn(ADQLColumn, SearchColumnList)
 	 * @see #checkColumnReference(ColumnReference, ClauseSelect, SearchColumnList)
 	 */
+	@Override
 	public void check(final ADQLQuery query) throws ParseException{
 		UnresolvedIdentifiersException errors = new UnresolvedIdentifiersException();
 		HashMap<DBTable,ADQLTable> mapTables = new HashMap<DBTable,ADQLTable>();
@@ -215,14 +213,13 @@ public class DBChecker implements QueryChecker {
 			}
 		}
 
-		SearchColumnList list = query.getFrom().getDBColumns();
-
-		//		// DEBUG
-		//		System.out.println("\n*** FROM COLUMNS ***");
-		//		for(DBColumn dbCol : list){
-		//			System.out.println("\t- "+dbCol.getADQLName()+" in "+((dbCol.getTable()==null)?"<NULL>":dbCol.getTable().getADQLName())+" (= "+dbCol.getDBName()+" in "+((dbCol.getTable()==null)?"<NULL>":dbCol.getTable().getDBName())+")");
-		//		}
-		//		System.out.println();
+		SearchColumnList list;
+		try{
+			list = query.getFrom().getDBColumns();
+		}catch(ParseException pe){
+			errors.addException(pe);
+			list = new SearchColumnList();
+		}
 
 		// Check the existence of all columns:
 		sHandler = new SearchColumnHandler();
diff --git a/src/adql/db/DBCommonColumn.java b/src/adql/db/DBCommonColumn.java
new file mode 100644
index 0000000000000000000000000000000000000000..8a5edab1b00da140912df15a854b1dabd2309c73
--- /dev/null
+++ b/src/adql/db/DBCommonColumn.java
@@ -0,0 +1,159 @@
+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 2014 - Astronomishes Rechen Institute (ARI)
+ */
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+import adql.query.ADQLQuery;
+
+/**
+ * This is a special column which exists only after a NATURAL JOIN or a JOIN ... USING between two tables.
+ * It lets unify several columns of the joined tables in a single column.
+ * 
+ * Thus, the writer of an ADQL query can use the column name without table prefix (since after the join there will be only one)
+ * or with a prefix table of the joined tables. The list of all covered tables is stored in this object and can be extended
+ * in case of several JOINs.
+ * 
+ * @author Gr&eacute;gory Mantelet (ARI) - gmantele@ari.uni-heidelberg.de
+ * @version 1.1 (11/2013)
+ */
+public class DBCommonColumn implements DBColumn {
+
+	protected DBColumn generalColumnDesc;
+	protected ArrayList<DBTable> lstCoveredTables = new ArrayList<DBTable>();
+
+	/**
+	 * Create a column which merges both of the given columns.
+	 * 
+	 * This special {@link DBColumn} implementation is not associated with one table,
+	 * and can be listed in a {@link DBTable} ONLY IF the latter is the result of a sub-query (see {@link ADQLQuery#getResultingColumns()}).
+	 * 
+	 * A column resulting from a tables join is common only to the joined tables. That's why a list of all tables covered
+	 * by this column is created or update at each merge. It can be accessed thanks to {@link #getCoveredTables()}.
+	 * 
+	 * Note: In the case one or both of the columns to join are {@link DBCommonColumn}, the list of their covered tables are also merged.
+	 * 
+	 * @param leftCol	Column of the left join table. May be a {@link DBCommonColumn}.
+	 * @param rightCol	Column of the right join table. May be a {@link DBCommonColumn}.
+	 */
+	public DBCommonColumn(final DBColumn leftCol, final DBColumn rightCol){
+
+		// LEFT COLUMN:
+		if (leftCol instanceof DBCommonColumn){
+			// set the general column description:
+			generalColumnDesc = ((DBCommonColumn)leftCol).generalColumnDesc;
+
+			// add all covered tables of the left common column:
+			Iterator<DBTable> it = ((DBCommonColumn)leftCol).getCoveredTables();
+			while(it.hasNext())
+				addCoveredTable(it.next());
+		}else{
+			// set the general column description:
+			generalColumnDesc = leftCol.copy(leftCol.getDBName(), leftCol.getADQLName(), null);
+			// add the table to cover:
+			addCoveredTable(leftCol.getTable());
+		}
+
+		// RIGHT COLUMN:
+		if (rightCol instanceof DBCommonColumn){
+			// add all covered tables of the left common column:
+			Iterator<DBTable> it = ((DBCommonColumn)rightCol).getCoveredTables();
+			while(it.hasNext())
+				addCoveredTable(it.next());
+		}else{
+			// add the table to cover:
+			addCoveredTable(rightCol.getTable());
+		}
+
+	}
+
+	/**
+	 * Constructor by copy.
+	 * It returns a copy of this instance of {@link DBCommonColumn}.
+	 * 
+	 * Note: The list of covered tables is NOT deeply copied!
+	 * 
+	 * @param toCopy	The {@link DBCommonColumn} to copy.
+	 * @param dbName	The new DB name of this {@link DBCommonColumn}.
+	 * @param adqlName	The new ADQL name of this {@link DBCommonColumn}.
+	 */
+	@SuppressWarnings("unchecked")
+	public DBCommonColumn(final DBCommonColumn toCopy, final String dbName, final String adqlName){
+		generalColumnDesc = toCopy.generalColumnDesc.copy(dbName, adqlName, null);
+		lstCoveredTables = (ArrayList<DBTable>)toCopy.lstCoveredTables.clone();
+	}
+
+	@Override
+	public final String getADQLName(){
+		return generalColumnDesc.getADQLName();
+	}
+
+	@Override
+	public final String getDBName(){
+		return generalColumnDesc.getDBName();
+	}
+
+	@Override
+	public final DBTable getTable(){
+		return null;
+	}
+
+	/**
+	 * Get an iterator over the list of all tables covered by this common column.
+	 * 
+	 * @return	Iterator over all covered tables.
+	 */
+	public final Iterator<DBTable> getCoveredTables(){
+		return lstCoveredTables.iterator();
+	}
+
+	/**
+	 * Add a table that this common column must cover from now.
+	 * 
+	 * Warning: no unicity check is never done !
+	 * 
+	 * @param table	Table to add in the covered tables list.
+	 */
+	protected void addCoveredTable(final DBTable table){
+		if (table != null)
+			lstCoveredTables.add(table);
+	}
+
+	/**
+	 * WARNING: This copy function does not make a real copy of this DBCommonColumn !
+	 *          It returns a modified copy of the general column description it contains.
+	 * 
+	 * Note: To make a real copy of this DBCommonColumn use the Constructor by copy {@link #DBCommonColumn(DBCommonColumn, String, String)}.
+	 * 
+	 * @param dbName	Its new DB name.
+	 * @param adqlName	Its new ADQL name.
+	 * @param dbTable	Its new DBTable
+	 * 
+	 * @return			A modified copy of the general column description this common column represents.
+	 * 
+	 * @see adql.db.DBColumn#copy(java.lang.String, java.lang.String, adql.db.DBTable)
+	 */
+	@Override
+	public DBColumn copy(final String dbName, final String adqlName, final DBTable dbTable){
+		return generalColumnDesc.copy(dbName, adqlName, dbTable);
+	}
+
+}
diff --git a/src/adql/db/DefaultDBTable.java b/src/adql/db/DefaultDBTable.java
index b26c668ddb5d2d16d493ce5cc737abf032ae206d..4bb7e6aec5d46910eb184719774a477513276918 100644
--- a/src/adql/db/DefaultDBTable.java
+++ b/src/adql/db/DefaultDBTable.java
@@ -16,7 +16,8 @@ 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 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ * Copyright 2012-2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomisches Rechen Institute (ARI)
  */
 
 import java.util.Collection;
@@ -26,8 +27,8 @@ import java.util.Iterator;
 /**
  * Default implementation of {@link DBTable}.
  * 
- * @author Gr&eacute;gory Mantelet (CDS)
- * @version 08/2011
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 1.1 (11/2013)
  */
 public class DefaultDBTable implements DBTable {
 
@@ -125,18 +126,22 @@ public class DefaultDBTable implements DBTable {
 		adqlCatalogName = adqlCatName;
 	}
 
+	@Override
 	public final String getDBName(){
 		return dbName;
 	}
 
+	@Override
 	public final String getDBSchemaName(){
 		return dbSchemaName;
 	}
 
+	@Override
 	public final String getDBCatalogName(){
 		return dbCatalogName;
 	}
 
+	@Override
 	public final String getADQLName(){
 		return adqlName;
 	}
@@ -145,6 +150,7 @@ public class DefaultDBTable implements DBTable {
 		adqlName = (name != null) ? name : dbName;
 	}
 
+	@Override
 	public final String getADQLSchemaName(){
 		return adqlSchemaName;
 	}
@@ -153,6 +159,7 @@ public class DefaultDBTable implements DBTable {
 		adqlSchemaName = (name != null) ? name : dbSchemaName;
 	}
 
+	@Override
 	public final String getADQLCatalogName(){
 		return adqlCatalogName;
 	}
@@ -167,6 +174,7 @@ public class DefaultDBTable implements DBTable {
 	 * 
 	 * @see adql.db.DBTable#getColumn(java.lang.String, boolean)
 	 */
+	@Override
 	public DBColumn getColumn(String colName, boolean byAdqlName){
 		if (byAdqlName)
 			return columns.get(colName);
@@ -183,6 +191,7 @@ public class DefaultDBTable implements DBTable {
 		return (getColumn(colName, byAdqlName) != null);
 	}
 
+	@Override
 	public Iterator<DBColumn> iterator(){
 		return columns.values().iterator();
 	}
@@ -238,10 +247,15 @@ public class DefaultDBTable implements DBTable {
 		return splitRes;
 	}
 
+	@Override
 	public DBTable copy(final String dbName, final String adqlName){
 		DefaultDBTable copy = new DefaultDBTable(dbName, adqlName);
-		for(DBColumn col : this)
-			copy.addColumn(col.copy(col.getDBName(), col.getADQLName(), copy));
+		for(DBColumn col : this){
+			if (col instanceof DBCommonColumn)
+				copy.addColumn(new DBCommonColumn((DBCommonColumn)col, col.getDBName(), col.getADQLName()));
+			else
+				copy.addColumn(col.copy(col.getDBName(), col.getADQLName(), copy));
+		}
 		return copy;
 	}
 
diff --git a/src/adql/db/SearchColumnList.java b/src/adql/db/SearchColumnList.java
index 1149f379ad637815a8e48f5293c3380c5a6d02c9..7d13a72f97a58d2fe657cbec6a8c6413f70b3fa8 100644
--- a/src/adql/db/SearchColumnList.java
+++ b/src/adql/db/SearchColumnList.java
@@ -16,18 +16,20 @@ 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 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ * Copyright 2012-2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomishes Rechen Institute (ARI)
  */
 
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.Map;
+import java.util.NoSuchElementException;
 
 import adql.query.IdentifierField;
-
+import adql.query.from.ADQLJoin;
 import adql.query.operand.ADQLColumn;
-
 import cds.utils.TextualSearchList;
 
 /**
@@ -43,8 +45,8 @@ import cds.utils.TextualSearchList;
  * 	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)
- * @version 09/2011
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 1.1 (11/2013)
  */
 public class SearchColumnList extends TextualSearchList<DBColumn> {
 	private static final long serialVersionUID = 1L;
@@ -216,69 +218,124 @@ public class SearchColumnList extends TextualSearchList<DBColumn> {
 	 * @see IdentifierField
 	 */
 	public ArrayList<DBColumn> search(final String catalog, final String schema, final String table, final String column, final byte caseSensitivity){
+
 		ArrayList<DBColumn> tmpResult = get(column, IdentifierField.COLUMN.isCaseSensitive(caseSensitivity));
 
+		/* WITH TABLE PREFIX */
 		if (table != null){
-			ArrayList<DBColumn> result = new ArrayList<DBColumn>();
+			/* 1. Figure out the table alias */
+			String tableName = null;
+			ArrayList<String> aliasMatches = null;
+
+			// Case sensitive => tableName is set , aliasMatches = null
+			if (IdentifierField.TABLE.isCaseSensitive(caseSensitivity)){
+				tableName = tableAliases.get(table);
+				if (tableName == null)
+					tableName = table;
+			}
+			// 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{
+				aliasMatches = mapAliases.get(table.toLowerCase());
+				if (aliasMatches == null || aliasMatches.isEmpty())
+					tableName = table;
+			}
 
+			/* 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){
-				DBTable dbTable = match.getTable();
-				if (IdentifierField.TABLE.isCaseSensitive(caseSensitivity)){
-					String tableName = tableAliases.get(table);
-					if (tableName == null)
-						tableName = table;
-					if (!dbTable.getADQLName().equals(tableName))
-						continue;
-				}else{
-					ArrayList<String> aliases = mapAliases.get(table.toLowerCase());
-					if (aliases == null){
-						if (!dbTable.getADQLName().equalsIgnoreCase(table))
-							continue;
-					}else{
+
+				// Get the list of all tables covered by this column:
+				//   - only 1 if it is a normal column
+				//   - several if it is a common column (= result of table join)
+				Iterator<DBTable> itMatchTables;
+				if (ADQLJoin.isCommonColumn(match))
+					itMatchTables = ((DBCommonColumn)match).getCoveredTables();
+				else
+					itMatchTables = new SingleIterator<DBTable>(match.getTable());
+
+				// Test the matching with every covered tables:
+				DBTable matchTable;
+				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 (!matchTable.getADQLName().equals(tableName))
+								continue;
+						}else{
+							if (!matchTable.getADQLName().equalsIgnoreCase(tableName))
+								continue;
+						}
+					}else{	// case INsensitive with at least one alias found
 						boolean foundAlias = false;
 						String temp;
-						for(int a = 0; !foundAlias && a < aliases.size(); a++){
-							temp = tableAliases.get(aliases.get(a));
+						for(int a = 0; !foundAlias && a < aliasMatches.size(); a++){
+							temp = tableAliases.get(aliasMatches.get(a));
 							if (temp != null)
-								foundAlias = dbTable.getADQLName().equalsIgnoreCase(temp);
+								foundAlias = matchTable.getADQLName().equalsIgnoreCase(temp);
 						}
 						if (!foundAlias)
 							continue;
 					}
-				}
-
-				if (schema != null){
-					if (IdentifierField.SCHEMA.isCaseSensitive(caseSensitivity)){
-						if (!dbTable.getADQLSchemaName().equals(schema))
-							continue;
-					}else{
-						if (!dbTable.getADQLSchemaName().equalsIgnoreCase(schema))
-							continue;
-					}
 
-					if (catalog != null){
-						if (IdentifierField.CATALOG.isCaseSensitive(caseSensitivity)){
-							if (!dbTable.getADQLCatalogName().equals(catalog))
+					// test the schema name:
+					if (schema != null){
+						if (IdentifierField.SCHEMA.isCaseSensitive(caseSensitivity)){
+							if (!matchTable.getADQLSchemaName().equals(schema))
 								continue;
 						}else{
-							if (!dbTable.getADQLCatalogName().equalsIgnoreCase(catalog))
+							if (!matchTable.getADQLSchemaName().equalsIgnoreCase(schema))
 								continue;
 						}
+
+						// test the catalog name:
+						if (catalog != null){
+							if (IdentifierField.CATALOG.isCaseSensitive(caseSensitivity)){
+								if (!matchTable.getADQLCatalogName().equals(catalog))
+									continue;
+							}else{
+								if (!matchTable.getADQLCatalogName().equalsIgnoreCase(catalog))
+									continue;
+							}
+						}
 					}
-				}
 
-				result.add(match);
+					// if here, all prefixes are matching and so the column is a good match:
+					DBColumn goodMatch = matchTable.getColumn(match.getADQLName(), true);
+					System.out.println("Good match for \"" + catalog + "." + schema + "." + table + "." + column + "\" found: " + goodMatch);
+					result.add(goodMatch);
+				}
 			}
 			return result;
 
-		}else{
+		}
+		/* NO TABLE PREFIX */
+		else{
 			// Special case: the columns merged by a NATURAL JOIN or a USING may have no table reference:
 			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 (tmpResult.get(i).getTable() == null)
-						result.add(tmpResult.remove(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
+						DBCommonColumn commonColumn = (DBCommonColumn)tmpResult.remove(i);
+						result.add(commonColumn);
+						// then, add all normal columns covered by this common columns:
+						Iterator<DBTable> itCoveredTables = commonColumn.getCoveredTables();
+						while(itCoveredTables.hasNext())
+							tmpResult.add(itCoveredTables.next().getColumn(column, true));
+					}
 				}
+
 				if (result.size() == 1)
 					return result;
 			}
@@ -335,9 +392,47 @@ public class SearchColumnList extends TextualSearchList<DBColumn> {
 	 * @version 09/2011
 	 */
 	private static class DBColumnKeyExtractor implements KeyExtractor<DBColumn> {
+		@Override
 		public String getKey(DBColumn obj){
 			return obj.getADQLName();
 		}
 	}
 
+	/**
+	 * 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.1 (11/2013)
+	 * @since 1.1
+	 */
+	private static class SingleIterator< E > implements Iterator<E> {
+		private final E item;
+		private boolean done = false;
+
+		public SingleIterator(final E singleItem){
+			item = singleItem;
+		}
+
+		@Override
+		public boolean hasNext(){
+			return !done;
+		}
+
+		@Override
+		public E next(){
+			if (!done){
+				done = true;
+				return item;
+			}else
+				throw new NoSuchElementException();
+		}
+
+		@Override
+		public void remove(){
+			throw new UnsupportedOperationException();
+		}
+	}
+
 }
diff --git a/src/adql/db/exception/UnresolvedJoin.java b/src/adql/db/exception/UnresolvedJoin.java
new file mode 100644
index 0000000000000000000000000000000000000000..e4789b70dadf64a33eb0034526d972c8b7d3115e
--- /dev/null
+++ b/src/adql/db/exception/UnresolvedJoin.java
@@ -0,0 +1,55 @@
+package adql.db.exception;
+
+/*
+ * 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 2013-2014 - Astronomishes Rechen Institute (ARI)
+ */
+
+import adql.parser.ParseException;
+import adql.query.TextPosition;
+
+/**
+ * This exception is thrown when a table between 2 tables can not be resolved,
+ * and particularly because of the join condition (i.e. column names not found, ...).
+ * 
+ * @author Gr&eacute;gory Mantelet (ARI) - gmantele@ari.uni-heidelberg.de
+ * @version 1.1 (11/2013)
+ */
+public class UnresolvedJoin extends ParseException {
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * Build a simple UnresolvedJoin.
+	 * It is generally used when a column can not be resolved (linked to one of the joined tables).
+	 * 
+	 * @param message	Message to display explaining why the join can't be resolved.
+	 */
+	public UnresolvedJoin(String message){
+		super(message);
+	}
+
+	/**
+	 * Build an UnresolvedJoin and specify, in addition of the error message, the position of the column not resolved.
+	 * 
+	 * @param message		Message to display explaining why the join can't be resolved.
+	 * @param errorPosition	Position of the wrong part of the join.
+	 */
+	public UnresolvedJoin(String message, TextPosition errorPosition){
+		super(message, errorPosition);
+	}
+
+}
diff --git a/src/adql/query/ADQLQuery.java b/src/adql/query/ADQLQuery.java
index f868009b49c25e03b3ac96e3f9db248f39c84ffc..58513b45779c90862940a1368573e27a5e98b615 100644
--- a/src/adql/query/ADQLQuery.java
+++ b/src/adql/query/ADQLQuery.java
@@ -16,7 +16,8 @@ package adql.query;
  * You should have received a copy of the GNU Lesser General Public License
  * along with ADQLLibrary.  If not, see <http://www.gnu.org/licenses/>.
  * 
- * Copyright 2012 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ * Copyright 2012-2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomishes Rechen Institute (ARI)
  */
 
 import java.util.ArrayList;
@@ -25,22 +26,19 @@ import java.util.NoSuchElementException;
 
 import adql.db.DBColumn;
 import adql.db.DefaultDBColumn;
-
 import adql.parser.ADQLParser;
-
+import adql.parser.ParseException;
 import adql.query.from.FromContent;
-
 import adql.query.operand.ADQLColumn;
 import adql.query.operand.ADQLOperand;
-
 import adql.search.ISearchHandler;
 
 /**
  * <p>Object representation of an ADQL query or sub-query.</p>
  * <p>The resulting object of the {@link ADQLParser} is an object of this class.</p>
  * 
- * @author Gr&eacute;gory Mantelet (CDS)
- * @version 01/2012
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 1.1 (11/2013)
  */
 public class ADQLQuery implements ADQLObject {
 
@@ -240,10 +238,12 @@ public class ADQLQuery implements ADQLObject {
 			orderBy = newOrderBy;
 	}
 
+	@Override
 	public ADQLObject getCopy() throws Exception{
 		return new ADQLQuery(this);
 	}
 
+	@Override
 	public String getName(){
 		return "{ADQL query}";
 	}
@@ -261,7 +261,11 @@ public class ADQLQuery implements ADQLObject {
 		for(SelectItem item : select){
 			ADQLOperand operand = item.getOperand();
 			if (item instanceof SelectAllColumns){
-				columns.addAll(from.getDBColumns());
+				try{
+					columns.addAll(from.getDBColumns());
+				}catch(ParseException pe){
+					// Here, this error should not occur any more, since it must have been caught by the DBChecker!
+				}
 			}else{
 				DBColumn col = null;
 				if (item.hasAlias()){
@@ -296,12 +300,14 @@ public class ADQLQuery implements ADQLObject {
 		return sHandler.iterator();
 	}
 
+	@Override
 	public ADQLIterator adqlIterator(){
 		return new ADQLIterator(){
 
 			private int index = -1;
 			private ClauseADQL<?> currentClause = null;
 
+			@Override
 			public ADQLObject next(){
 				index++;
 				switch(index){
@@ -329,10 +335,12 @@ public class ADQLQuery implements ADQLObject {
 				return currentClause;
 			}
 
+			@Override
 			public boolean hasNext(){
 				return index + 1 < 6;
 			}
 
+			@Override
 			@SuppressWarnings("unchecked")
 			public void replace(ADQLObject replacer) throws UnsupportedOperationException, IllegalStateException{
 				if (index <= -1)
@@ -382,6 +390,7 @@ public class ADQLQuery implements ADQLObject {
 				}
 			}
 
+			@Override
 			public void remove(){
 				if (index <= -1)
 					throw new IllegalStateException("remove() impossible: next() has not yet been called !");
@@ -394,6 +403,7 @@ public class ADQLQuery implements ADQLObject {
 		};
 	}
 
+	@Override
 	public String toADQL(){
 		StringBuffer adql = new StringBuffer(select.toADQL());
 		adql.append("\nFROM ").append(from.toADQL());
diff --git a/src/adql/query/from/ADQLJoin.java b/src/adql/query/from/ADQLJoin.java
index 3f4736233eeac52a03e334fe0c6dea8f02ec30bb..e9bdbb42ed6f4f4f5d7390d139d878612f419ab3 100644
--- a/src/adql/query/from/ADQLJoin.java
+++ b/src/adql/query/from/ADQLJoin.java
@@ -16,26 +16,32 @@ package adql.query.from;
  * You should have received a copy of the GNU Lesser General Public License
  * along with ADQLLibrary.  If not, see <http://www.gnu.org/licenses/>.
  * 
- * Copyright 2012 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ * Copyright 2012-2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomishes Rechen Institute (ARI)
  */
 
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.Iterator;
+import java.util.Map;
 import java.util.NoSuchElementException;
 
 import adql.db.DBColumn;
+import adql.db.DBCommonColumn;
 import adql.db.SearchColumnList;
+import adql.db.exception.UnresolvedJoin;
 import adql.query.ADQLIterator;
 import adql.query.ADQLObject;
 import adql.query.ClauseConstraints;
+import adql.query.IdentifierField;
 import adql.query.operand.ADQLColumn;
 
 /**
  * Defines a join between two "tables".
  * 
- * @author Gr&eacute;gory Mantelet (CDS)
- * @version 08/2011
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 1.1 (11/2013)
  */
 public abstract class ADQLJoin implements ADQLObject, FromContent {
 
@@ -179,14 +185,17 @@ public abstract class ADQLJoin implements ADQLObject, FromContent {
 	public final Iterator<ADQLColumn> getJoinedColumns(){
 		if (lstColumns == null){
 			return new Iterator<ADQLColumn>(){
+				@Override
 				public boolean hasNext(){
 					return false;
 				}
 
+				@Override
 				public ADQLColumn next(){
 					return null;
 				}
 
+				@Override
 				public void remove(){
 					;
 				}
@@ -225,10 +234,12 @@ public abstract class ADQLJoin implements ADQLObject, FromContent {
 	/* ***************** */
 	/* INHERITED METHODS */
 	/* ***************** */
+	@Override
 	public String getName(){
 		return getJoinType();
 	}
 
+	@Override
 	public ADQLIterator adqlIterator(){
 		return new ADQLIterator(){
 
@@ -237,6 +248,7 @@ public abstract class ADQLJoin implements ADQLObject, FromContent {
 			private final int offset = 2 + ((condition == null) ? 0 : 1);
 			private Iterator<ADQLColumn> itCol = null;
 
+			@Override
 			public ADQLObject next(){
 				index++;
 
@@ -254,10 +266,12 @@ public abstract class ADQLJoin implements ADQLObject, FromContent {
 					throw new NoSuchElementException();
 			}
 
+			@Override
 			public boolean hasNext(){
 				return (itCol != null && itCol.hasNext()) || index + 1 < nbItems;
 			}
 
+			@Override
 			public void replace(ADQLObject replacer) throws UnsupportedOperationException, IllegalStateException{
 				if (index <= -1)
 					throw new IllegalStateException("replace(ADQLObject) impossible: next() has not yet been called !");
@@ -288,6 +302,7 @@ public abstract class ADQLJoin implements ADQLObject, FromContent {
 
 			}
 
+			@Override
 			public void remove(){
 				if (index <= -1)
 					throw new IllegalStateException("remove() impossible: next() has not yet been called !");
@@ -305,6 +320,7 @@ public abstract class ADQLJoin implements ADQLObject, FromContent {
 		};
 	}
 
+	@Override
 	public String toADQL(){
 		StringBuffer adql = new StringBuffer(leftTable.toADQL());
 
@@ -323,38 +339,111 @@ public abstract class ADQLJoin implements ADQLObject, FromContent {
 		return adql.toString();
 	}
 
-	public SearchColumnList getDBColumns(){
+	@Override
+	public SearchColumnList getDBColumns() throws UnresolvedJoin{
 		SearchColumnList list = new SearchColumnList();
+		SearchColumnList leftList = leftTable.getDBColumns();
+		SearchColumnList rightList = rightTable.getDBColumns();
 
-		SearchColumnList leftList = leftTable.getDBColumns(), rightList = rightTable.getDBColumns();
-		list.addAll(leftList);
-		list.addAll(rightList);
-
+		/* 1. Figure out duplicated columns */
+		HashMap<String,DBCommonColumn> mapDuplicated = new HashMap<String,DBCommonColumn>();
+		// CASE: NATURAL
 		if (natural){
-			SearchColumnList doublons = new SearchColumnList(leftList);
-			doublons.retainAll(rightList);
-			for(DBColumn dbCol : doublons)
-				list.add(dbCol.copy(dbCol.getDBName(), dbCol.getADQLName(), null));	// makes a copy which keeps the same DB and ADQL name but set to NULL the dbTable
-		}else if (lstColumns != null){
-			for(ADQLColumn col : lstColumns){
-				ArrayList<DBColumn> dbCols = list.search(col.getCatalogName(), col.getSchemaName(), col.getTableName(), col.getColumnName(), col.getCaseSensitive());
-				if (dbCols.size() > 0){
-					DBColumn match = dbCols.get(0);
-					list.add(match.copy(match.getDBName(), match.getADQLName(), null)); // makes a copy which keeps the same DB and ADQL name but set to NULL the dbTable
+			// Find duplicated items between the two lists and add one common column in mapDuplicated for each
+			DBColumn rightCol;
+			for(DBColumn leftCol : leftList){
+				// search for at most one column with the same name in the RIGHT list
+				// and throw an exception is there are several matches:
+				rightCol = findAtMostOneColumn(leftCol.getADQLName(), (byte)0, rightList, false);
+				// if there is one...
+				if (rightCol != null){
+					// ...check there is only one column with this name in the LEFT list,
+					// and throw an exception if it is not the case:
+					findExactlyOneColumn(leftCol.getADQLName(), (byte)0, leftList, true);
+					// ...create a common column:
+					mapDuplicated.put(leftCol.getADQLName().toLowerCase(), new DBCommonColumn(leftCol, rightCol));
 				}
 			}
 
 		}
+		// CASE: USING
+		else if (lstColumns != null && !lstColumns.isEmpty()){
+			// For each columns of usingList, check there is in each list exactly one matching column, and then, add it in mapDuplicated
+			DBColumn leftCol, rightCol;
+			for(ADQLColumn usingCol : lstColumns){
+				// search for exactly one column with the same name in the LEFT list
+				// and throw an exception if there is none, or if there are several matches:
+				leftCol = findExactlyOneColumn(usingCol.getColumnName(), usingCol.getCaseSensitive(), leftList, true);
+				// idem in the RIGHT list:
+				rightCol = findExactlyOneColumn(usingCol.getColumnName(), usingCol.getCaseSensitive(), rightList, false);
+				// create a common column:
+				mapDuplicated.put((usingCol.isCaseSensitive(IdentifierField.COLUMN) ? ("\"" + usingCol.getColumnName() + "\"") : usingCol.getColumnName().toLowerCase()), new DBCommonColumn(leftCol, rightCol));
+			}
+
+		}
+		// CASE: NO DUPLICATION TO FIGURE OUT
+		else{
+			// Return the union of both lists:
+			list.addAll(leftList);
+			list.addAll(rightList);
+			return list;
+		}
+
+		/* 2. Add all columns of the left list except the ones identified as duplications */
+		addAllExcept(leftList, list, mapDuplicated);
+
+		/* 3. Add all columns of the right list except the ones identified as duplications */
+		addAllExcept(rightList, list, mapDuplicated);
+
+		/* 4. Add all common columns of mapDuplicated */
+		list.addAll(mapDuplicated.values());
 
 		return list;
 	}
 
+	public final static void addAllExcept(final SearchColumnList itemsToAdd, final SearchColumnList target, final Map<String,DBCommonColumn> exception){
+		for(DBColumn col : itemsToAdd){
+			if (!exception.containsKey(col.getADQLName().toLowerCase()) && !exception.containsKey("\"" + col.getADQLName() + "\""))
+				target.add(col);
+		}
+	}
+
+	public final static DBColumn findExactlyOneColumn(final String columnName, final byte caseSensitive, final SearchColumnList list, final boolean leftList) throws UnresolvedJoin{
+		DBColumn result = findAtMostOneColumn(columnName, caseSensitive, list, leftList);
+		if (result == null)
+			throw new UnresolvedJoin("Column \"" + columnName + "\" specified in USING clause does not exist in " + (leftList ? "left" : "right") + " table!");
+		else
+			return result;
+	}
+
+	public final static DBColumn findAtMostOneColumn(final String columnName, final byte caseSensitive, final SearchColumnList list, final boolean leftList) throws UnresolvedJoin{
+		ArrayList<DBColumn> result = list.search(null, null, null, columnName, caseSensitive);
+		if (result.isEmpty())
+			return null;
+		else if (result.size() > 1)
+			throw new UnresolvedJoin("Common column name \"" + columnName + "\" appears more than once in " + (leftList ? "left" : "right") + " table!");
+		else
+			return result.get(0);
+	}
+
+	/**
+	 * Tells whether the given column is a common column (that's to say, a unification of several columns of the same name).
+	 * 
+	 * @param col	A DBColumn.
+	 * @return		true if the given column is a common column, false otherwise (particularly if col = null).
+	 */
+	public static final boolean isCommonColumn(final DBColumn col){
+		return (col != null && col instanceof DBCommonColumn);
+	}
+
+	@Override
 	public ArrayList<ADQLTable> getTables(){
 		ArrayList<ADQLTable> tables = leftTable.getTables();
 		tables.addAll(rightTable.getTables());
 		return tables;
 	}
 
+	@Override
 	public ArrayList<ADQLTable> getTablesByAlias(final String alias, final boolean caseSensitive){
 		ArrayList<ADQLTable> tables = leftTable.getTablesByAlias(alias, caseSensitive);
 		tables.addAll(rightTable.getTablesByAlias(alias, caseSensitive));
@@ -371,6 +460,7 @@ public abstract class ADQLJoin implements ADQLObject, FromContent {
 	 */
 	public abstract String getJoinType();
 
+	@Override
 	public abstract ADQLObject getCopy() throws Exception;
 
 }
\ No newline at end of file
diff --git a/src/adql/query/from/FromContent.java b/src/adql/query/from/FromContent.java
index 4d29d033a517cef3ad5d1cd7f77818f83f1a4026..2a502f0566c1868ae84d03ad361202909b0c855c 100644
--- a/src/adql/query/from/FromContent.java
+++ b/src/adql/query/from/FromContent.java
@@ -16,22 +16,23 @@ package adql.query.from;
  * You should have received a copy of the GNU Lesser General Public License
  * along with ADQLLibrary.  If not, see <http://www.gnu.org/licenses/>.
  * 
- * Copyright 2012 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ * Copyright 2012-2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomishes Rechen Institute (ARI)
  */
 
 import java.util.ArrayList;
 
 import adql.db.DBColumn;
 import adql.db.SearchColumnList;
-
+import adql.db.exception.UnresolvedJoin;
 import adql.query.ADQLObject;
 
 /**
  * Represents the content of the whole or a part of the clause FROM.
  * It could be either a table ({@link ADQLTable}) or a join ({@link ADQLJoin}).
  * 
- * @author Gr&eacute;gory Mantelet (CDS)
- * @version 01/2012
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 1.1 (11/2013)
  */
 public interface FromContent extends ADQLObject {
 
@@ -41,8 +42,9 @@ public interface FromContent extends ADQLObject {
 	 * <p><i><u>Note:</u> In the most cases, this list is generated on the fly !</i></p>
 	 * 
 	 * @return	All the available {@link DBColumn}s.
+	 * @throws UnresolvedJoin If a join is not possible.
 	 */
-	public SearchColumnList getDBColumns();
+	public SearchColumnList getDBColumns() throws UnresolvedJoin;
 
 	/**
 	 * Gets all {@link ADQLTable} instances contained in this FROM part (itself included, if it is an {@link ADQLTable}).
diff --git a/src/adql/translator/PostgreSQLTranslator.java b/src/adql/translator/PostgreSQLTranslator.java
index e49348089a44a6ee2cf38821ce0131df49215c90..5769fb72c5c330f1aae7df1490c45d63f17b162a 100644
--- a/src/adql/translator/PostgreSQLTranslator.java
+++ b/src/adql/translator/PostgreSQLTranslator.java
@@ -16,7 +16,8 @@ package adql.translator;
  * You should have received a copy of the GNU Lesser General Public License
  * along with ADQLLibrary.  If not, see <http://www.gnu.org/licenses/>.
  * 
- * Copyright 2012 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ * Copyright 2012-2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomisches Rechen Institute (ARI)
  */
 
 import java.util.ArrayList;
@@ -25,7 +26,7 @@ import java.util.Iterator;
 
 import adql.db.DBColumn;
 import adql.db.DBTable;
-
+import adql.db.exception.UnresolvedJoin;
 import adql.query.ADQLList;
 import adql.query.ADQLObject;
 import adql.query.ADQLOrder;
@@ -36,7 +37,6 @@ import adql.query.ColumnReference;
 import adql.query.IdentifierField;
 import adql.query.SelectAllColumns;
 import adql.query.SelectItem;
-
 import adql.query.constraint.ADQLConstraint;
 import adql.query.constraint.Between;
 import adql.query.constraint.Comparison;
@@ -44,11 +44,9 @@ import adql.query.constraint.Exists;
 import adql.query.constraint.In;
 import adql.query.constraint.IsNull;
 import adql.query.constraint.NotConstraint;
-
-import adql.query.from.FromContent;
 import adql.query.from.ADQLJoin;
 import adql.query.from.ADQLTable;
-
+import adql.query.from.FromContent;
 import adql.query.operand.ADQLColumn;
 import adql.query.operand.ADQLOperand;
 import adql.query.operand.Concatenation;
@@ -57,13 +55,11 @@ import adql.query.operand.NumericConstant;
 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.MathFunction;
 import adql.query.operand.function.SQLFunction;
 import adql.query.operand.function.SQLFunctionType;
 import adql.query.operand.function.UserDefinedFunction;
-
 import adql.query.operand.function.geometry.AreaFunction;
 import adql.query.operand.function.geometry.BoxFunction;
 import adql.query.operand.function.geometry.CentroidFunction;
@@ -73,11 +69,11 @@ import adql.query.operand.function.geometry.DistanceFunction;
 import adql.query.operand.function.geometry.ExtractCoord;
 import adql.query.operand.function.geometry.ExtractCoordSys;
 import adql.query.operand.function.geometry.GeometryFunction;
+import adql.query.operand.function.geometry.GeometryFunction.GeometryValue;
 import adql.query.operand.function.geometry.IntersectsFunction;
 import adql.query.operand.function.geometry.PointFunction;
 import adql.query.operand.function.geometry.PolygonFunction;
 import adql.query.operand.function.geometry.RegionFunction;
-import adql.query.operand.function.geometry.GeometryFunction.GeometryValue;
 
 /**
  * <p>Translates all ADQL objects into the SQL adaptation of Postgres.</p>
@@ -86,8 +82,8 @@ import adql.query.operand.function.geometry.GeometryFunction.GeometryValue;
  * You will probably need to extend this translator to correctly manage the geometrical functions.
  * An extension is already available for PgSphere: {@link PgSphereTranslator}.</b></p>
  * 
- * @author Gr&eacute;gory Mantelet (CDS)
- * @version 01/2012
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 1.1 (11/2013)
  * 
  * @see PgSphereTranslator
  */
@@ -178,6 +174,7 @@ public class PostgreSQLTranslator implements ADQLTranslator {
 			return str.append(id);
 	}
 
+	@Override
 	@SuppressWarnings("unchecked")
 	public String translate(ADQLObject obj) throws TranslationException{
 		if (obj instanceof ADQLQuery)
@@ -200,6 +197,7 @@ public class PostgreSQLTranslator implements ADQLTranslator {
 			return obj.toADQL();
 	}
 
+	@Override
 	public String translate(ADQLQuery query) throws TranslationException{
 		StringBuffer sql = new StringBuffer(translate(query.getSelect()));
 
@@ -226,6 +224,7 @@ public class PostgreSQLTranslator implements ADQLTranslator {
 	/* *************************** */
 	/* ****** LIST & CLAUSE ****** */
 	/* *************************** */
+	@Override
 	public String translate(ADQLList<? extends ADQLObject> list) throws TranslationException{
 		if (list instanceof ClauseSelect)
 			return translate((ClauseSelect)list);
@@ -260,6 +259,7 @@ public class PostgreSQLTranslator implements ADQLTranslator {
 		return sql;
 	}
 
+	@Override
 	public String translate(ClauseSelect clause) throws TranslationException{
 		String sql = null;
 
@@ -275,10 +275,12 @@ public class PostgreSQLTranslator implements ADQLTranslator {
 		return sql;
 	}
 
+	@Override
 	public String translate(ClauseConstraints clause) throws TranslationException{
 		return getDefaultADQLList(clause);
 	}
 
+	@Override
 	public String translate(SelectItem item) throws TranslationException{
 		if (item instanceof SelectAllColumns)
 			return translate((SelectAllColumns)item);
@@ -293,6 +295,7 @@ public class PostgreSQLTranslator implements ADQLTranslator {
 		return translation.toString();
 	}
 
+	@Override
 	public String translate(SelectAllColumns item) throws TranslationException{
 		HashMap<String,String> mapAlias = new HashMap<String,String>();
 
@@ -306,7 +309,11 @@ public class PostgreSQLTranslator implements ADQLTranslator {
 				mapAlias.put(key, table.isCaseSensitive(IdentifierField.ALIAS) ? ("\"" + table.getAlias() + "\"") : table.getAlias());
 			}
 		}else if (item.getQuery() != null){
-			dbCols = item.getQuery().getFrom().getDBColumns();
+			try{
+				dbCols = item.getQuery().getFrom().getDBColumns();
+			}catch(UnresolvedJoin pe){
+				throw new TranslationException("Due to a join problem, the ADQL to SQL translation can not be completed!", pe);
+			}
 			ArrayList<ADQLTable> tables = item.getQuery().getFrom().getTables();
 			for(ADQLTable table : tables){
 				if (table.hasAlias()){
@@ -332,12 +339,13 @@ public class PostgreSQLTranslator implements ADQLTranslator {
 				appendIdentifier(cols, col.getDBName(), IdentifierField.COLUMN);
 				cols.append(" AS \"").append(col.getADQLName()).append('\"');
 			}
-			return cols.toString();
+			return (cols.length() > 0) ? cols.toString() : item.toADQL();
 		}else{
 			return item.toADQL();
 		}
 	}
 
+	@Override
 	public String translate(ColumnReference ref) throws TranslationException{
 		if (ref instanceof ADQLOrder)
 			return translate((ADQLOrder)ref);
@@ -378,6 +386,7 @@ public class PostgreSQLTranslator implements ADQLTranslator {
 		}
 	}
 
+	@Override
 	public String translate(ADQLOrder order) throws TranslationException{
 		return getDefaultColumnReference(order) + (order.isDescSorting() ? " DESC" : " ASC");
 	}
@@ -385,6 +394,7 @@ public class PostgreSQLTranslator implements ADQLTranslator {
 	/* ************************** */
 	/* ****** TABLE & JOIN ****** */
 	/* ************************** */
+	@Override
 	public String translate(FromContent content) throws TranslationException{
 		if (content instanceof ADQLTable)
 			return translate((ADQLTable)content);
@@ -394,6 +404,7 @@ public class PostgreSQLTranslator implements ADQLTranslator {
 			return content.toADQL();
 	}
 
+	@Override
 	public String translate(ADQLTable table) throws TranslationException{
 		StringBuffer sql = new StringBuffer();
 
@@ -420,6 +431,7 @@ public class PostgreSQLTranslator implements ADQLTranslator {
 		return sql.toString();
 	}
 
+	@Override
 	public String translate(ADQLJoin join) throws TranslationException{
 		StringBuffer sql = new StringBuffer(translate(join.getLeftTable()));
 
@@ -453,6 +465,7 @@ public class PostgreSQLTranslator implements ADQLTranslator {
 	/* ********************* */
 	/* ****** OPERAND ****** */
 	/* ********************* */
+	@Override
 	public String translate(ADQLOperand op) throws TranslationException{
 		if (op instanceof ADQLColumn)
 			return translate((ADQLColumn)op);
@@ -474,6 +487,7 @@ public class PostgreSQLTranslator implements ADQLTranslator {
 			return op.toADQL();
 	}
 
+	@Override
 	public String translate(ADQLColumn column) throws TranslationException{
 		// Use its DB name if known:
 		if (column.getDBLink() != null){
@@ -500,26 +514,32 @@ public class PostgreSQLTranslator implements ADQLTranslator {
 			return column.getFullColumnName();
 	}
 
+	@Override
 	public String translate(Concatenation concat) throws TranslationException{
 		return translate((ADQLList<ADQLOperand>)concat);
 	}
 
+	@Override
 	public String translate(NegativeOperand negOp) throws TranslationException{
 		return "-" + translate(negOp.getOperand());
 	}
 
+	@Override
 	public String translate(NumericConstant numConst) throws TranslationException{
 		return numConst.getValue();
 	}
 
+	@Override
 	public String translate(StringConstant strConst) throws TranslationException{
 		return "'" + strConst.getValue() + "'";
 	}
 
+	@Override
 	public String translate(WrappedOperand op) throws TranslationException{
 		return "(" + translate(op.getOperand()) + ")";
 	}
 
+	@Override
 	public String translate(Operation op) throws TranslationException{
 		return translate(op.getLeftOperand()) + op.getOperation().toADQL() + translate(op.getRightOperand());
 	}
@@ -527,6 +547,7 @@ public class PostgreSQLTranslator implements ADQLTranslator {
 	/* ************************ */
 	/* ****** CONSTRAINT ****** */
 	/* ************************ */
+	@Override
 	public String translate(ADQLConstraint cons) throws TranslationException{
 		if (cons instanceof Comparison)
 			return translate((Comparison)cons);
@@ -544,26 +565,32 @@ public class PostgreSQLTranslator implements ADQLTranslator {
 			return cons.toADQL();
 	}
 
+	@Override
 	public String translate(Comparison comp) throws TranslationException{
 		return translate(comp.getLeftOperand()) + " " + comp.getOperator().toADQL() + " " + translate(comp.getRightOperand());
 	}
 
+	@Override
 	public String translate(Between comp) throws TranslationException{
 		return translate(comp.getLeftOperand()) + " BETWEEN " + translate(comp.getMinOperand()) + " AND " + translate(comp.getMaxOperand());
 	}
 
+	@Override
 	public String translate(Exists exists) throws TranslationException{
 		return "EXISTS(" + translate(exists.getSubQuery()) + ")";
 	}
 
+	@Override
 	public String translate(In in) throws TranslationException{
 		return translate(in.getOperand()) + " " + in.getName() + " (" + (in.hasSubQuery() ? translate(in.getSubQuery()) : translate(in.getValuesList())) + ")";
 	}
 
+	@Override
 	public String translate(IsNull isNull) throws TranslationException{
 		return translate(isNull.getColumn()) + " IS " + (isNull.isNotNull() ? "NOT " : "") + "NULL";
 	}
 
+	@Override
 	public String translate(NotConstraint notCons) throws TranslationException{
 		return "NOT " + translate(notCons.getConstraint());
 	}
@@ -571,6 +598,7 @@ public class PostgreSQLTranslator implements ADQLTranslator {
 	/* *********************** */
 	/* ****** FUNCTIONS ****** */
 	/* *********************** */
+	@Override
 	public String translate(ADQLFunction fct) throws TranslationException{
 		if (fct instanceof GeometryFunction)
 			return translate((GeometryFunction)fct);
@@ -602,6 +630,7 @@ public class PostgreSQLTranslator implements ADQLTranslator {
 		return sql + ")";
 	}
 
+	@Override
 	public String translate(SQLFunction fct) throws TranslationException{
 		if (fct.getType() == SQLFunctionType.COUNT_ALL)
 			return "COUNT(" + (fct.isDistinct() ? "DISTINCT " : "") + "*)";
@@ -609,6 +638,7 @@ public class PostgreSQLTranslator implements ADQLTranslator {
 			return fct.getName() + "(" + (fct.isDistinct() ? "DISTINCT " : "") + translate(fct.getParameter(0)) + ")";
 	}
 
+	@Override
 	public String translate(MathFunction fct) throws TranslationException{
 		switch(fct.getType()){
 			case LOG:
@@ -624,6 +654,7 @@ public class PostgreSQLTranslator implements ADQLTranslator {
 		}
 	}
 
+	@Override
 	public String translate(UserDefinedFunction fct) throws TranslationException{
 		return getDefaultADQLFunction(fct);
 	}
@@ -631,6 +662,7 @@ public class PostgreSQLTranslator implements ADQLTranslator {
 	/* *********************************** */
 	/* ****** GEOMETRICAL FUNCTIONS ****** */
 	/* *********************************** */
+	@Override
 	public String translate(GeometryFunction fct) throws TranslationException{
 		if (fct instanceof AreaFunction)
 			return translate((AreaFunction)fct);
@@ -678,54 +710,67 @@ public class PostgreSQLTranslator implements ADQLTranslator {
 			return getDefaultADQLFunction(fct);
 	}
 
+	@Override
 	public String translate(GeometryValue<? extends GeometryFunction> geomValue) throws TranslationException{
 		return translate(geomValue.getValue());
 	}
 
+	@Override
 	public String translate(ExtractCoord extractCoord) throws TranslationException{
 		return getDefaultGeometryFunction(extractCoord);
 	}
 
+	@Override
 	public String translate(ExtractCoordSys extractCoordSys) throws TranslationException{
 		return getDefaultGeometryFunction(extractCoordSys);
 	}
 
+	@Override
 	public String translate(AreaFunction areaFunction) throws TranslationException{
 		return getDefaultGeometryFunction(areaFunction);
 	}
 
+	@Override
 	public String translate(CentroidFunction centroidFunction) throws TranslationException{
 		return getDefaultGeometryFunction(centroidFunction);
 	}
 
+	@Override
 	public String translate(DistanceFunction fct) throws TranslationException{
 		return getDefaultGeometryFunction(fct);
 	}
 
+	@Override
 	public String translate(ContainsFunction fct) throws TranslationException{
 		return getDefaultGeometryFunction(fct);
 	}
 
+	@Override
 	public String translate(IntersectsFunction fct) throws TranslationException{
 		return getDefaultGeometryFunction(fct);
 	}
 
+	@Override
 	public String translate(BoxFunction box) throws TranslationException{
 		return getDefaultGeometryFunction(box);
 	}
 
+	@Override
 	public String translate(CircleFunction circle) throws TranslationException{
 		return getDefaultGeometryFunction(circle);
 	}
 
+	@Override
 	public String translate(PointFunction point) throws TranslationException{
 		return getDefaultGeometryFunction(point);
 	}
 
+	@Override
 	public String translate(PolygonFunction polygon) throws TranslationException{
 		return getDefaultGeometryFunction(polygon);
 	}
 
+	@Override
 	public String translate(RegionFunction region) throws TranslationException{
 		return getDefaultGeometryFunction(region);
 	}
diff --git a/test/adql/SearchColumnListTest.java b/test/adql/SearchColumnListTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..776a3435b09af5b21a2a71623e5594e28db1fbe6
--- /dev/null
+++ b/test/adql/SearchColumnListTest.java
@@ -0,0 +1,240 @@
+package adql;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import adql.db.DBColumn;
+import adql.db.DBCommonColumn;
+import adql.db.DBTable;
+import adql.db.SearchColumnList;
+import adql.db.exception.UnresolvedJoin;
+import adql.parser.ParseException;
+import adql.query.IdentifierField;
+import adql.query.operand.ADQLColumn;
+import tap.metadata.TAPColumn;
+import tap.metadata.TAPSchema;
+import tap.metadata.TAPTable;
+
+public class SearchColumnListTest {
+
+	public static void main(String[] args) throws ParseException{
+
+		/* SET THE TABLES AND COLUMNS NEEDED FOR THE TEST */
+		// Describe the available table:
+		TAPTable tableA = new TAPTable("A", "TABLE", "NATURAL JOIN Test table", null);
+		TAPTable tableB = new TAPTable("B", "TABLE", "NATURAL JOIN Test table", null);
+		TAPTable tableC = new TAPTable("C", "TABLE", "NATURAL JOIN Test table", null);
+		TAPTable tableD = new TAPTable("D", "TABLE", "NATURAL JOIN Test table", null);
+
+		// Describe its columns:
+		tableA.addColumn(new TAPColumn("id", "Object ID"));
+		tableA.addColumn(new TAPColumn("txta", "Text of table A"));
+		tableB.addColumn(new TAPColumn("id", "Object ID"));
+		tableB.addColumn(new TAPColumn("txtb", "Text of table B"));
+		tableC.addColumn(new TAPColumn("Id", "Object ID"));
+		tableC.addColumn(new TAPColumn("txta", "Text of table A"));
+		tableC.addColumn(new TAPColumn("txtc", "Text of table C"));
+		tableD.addColumn(new TAPColumn("id", "Object ID"));
+		tableD.addColumn(new TAPColumn("txta", "Text of table A"));
+		tableD.addColumn(new TAPColumn("txtd", "Text of table D"));
+
+		// List all available tables:
+		TAPSchema schema = new TAPSchema("public");
+		schema.addTable(tableA);
+		schema.addTable(tableB);
+		schema.addTable(tableC);
+		schema.addTable(tableD);
+
+		// Build the corresponding SearchColumnList:
+		SearchColumnList listA = new SearchColumnList();
+		for(DBColumn col : tableA)
+			listA.add(col);
+		SearchColumnList listB = new SearchColumnList();
+		for(DBColumn col : tableB)
+			listB.add(col);
+		SearchColumnList listC = new SearchColumnList();
+		for(DBColumn col : tableC)
+			listC.add(col);
+		SearchColumnList listD = new SearchColumnList();
+		for(DBColumn col : tableD)
+			listD.add(col);
+
+		/* TEST OF NATURAL JOIN */
+		System.out.println("### CROSS JOIN ###");
+		SearchColumnList crossJoin = join(listA, listB, false, null);
+
+		// DEBUG
+		for(DBColumn dbCol : crossJoin){
+			if (dbCol instanceof DBCommonColumn){
+				System.out.print("\t- " + dbCol.getADQLName() + " in " + ((dbCol.getTable() == null) ? "<NULL>" : dbCol.getTable().getADQLName()) + " (= " + dbCol.getDBName() + " in ");
+				Iterator<DBTable> it = ((DBCommonColumn)dbCol).getCoveredTables();
+				DBTable table;
+				while(it.hasNext()){
+					table = it.next();
+					System.out.print((table == null) ? "<NULL>" : table.getDBName() + ", ");
+				}
+				System.out.println(")");
+			}else
+				System.out.println("\t- " + dbCol.getADQLName() + " in " + ((dbCol.getTable() == null) ? "<NULL>" : dbCol.getTable().getADQLName()) + " (= " + dbCol.getDBName() + " in " + ((dbCol.getTable() == null) ? "<NULL>" : dbCol.getTable().getDBName()) + ")");
+		}
+		System.out.println();
+
+		/* TEST OF NATURAL JOIN */
+		System.out.println("### NATURAL JOIN ###");
+		SearchColumnList join1 = join(listA, listB, true, null);
+		SearchColumnList join2 = join(listC, listD, true, null);
+		//SearchColumnList join3 = join(join1, join2, true, null);
+
+		// DEBUG
+		for(DBColumn dbCol : join2){
+			if (dbCol instanceof DBCommonColumn){
+				System.out.print("\t- " + dbCol.getADQLName() + " in " + ((dbCol.getTable() == null) ? "<NULL>" : dbCol.getTable().getADQLName()) + " (= " + dbCol.getDBName() + " in ");
+				Iterator<DBTable> it = ((DBCommonColumn)dbCol).getCoveredTables();
+				DBTable table;
+				while(it.hasNext()){
+					table = it.next();
+					System.out.print((table == null) ? "<NULL>" : table.getDBName() + ", ");
+				}
+				System.out.println(")");
+			}else
+				System.out.println("\t- " + dbCol.getADQLName() + " in " + ((dbCol.getTable() == null) ? "<NULL>" : dbCol.getTable().getADQLName()) + " (= " + dbCol.getDBName() + " in " + ((dbCol.getTable() == null) ? "<NULL>" : dbCol.getTable().getDBName()) + ")");
+		}
+		System.out.println();
+
+		/* TEST OF JOIN USING 1 */
+		System.out.println("\n### USING JOIN 1 ###");
+		ArrayList<ADQLColumn> usingList = new ArrayList<ADQLColumn>();
+		usingList.add(new ADQLColumn("id"));
+		SearchColumnList joinUsing1 = join(join1, join2, false, usingList);
+
+		// DEBUG
+		for(DBColumn dbCol : joinUsing1){
+			if (dbCol instanceof DBCommonColumn){
+				System.out.print("\t- " + dbCol.getADQLName() + " in " + ((dbCol.getTable() == null) ? "<NULL>" : dbCol.getTable().getADQLName()) + " (= " + dbCol.getDBName() + " in ");
+				Iterator<DBTable> it = ((DBCommonColumn)dbCol).getCoveredTables();
+				DBTable table;
+				while(it.hasNext()){
+					table = it.next();
+					System.out.print((table == null) ? "<NULL>" : table.getDBName() + ", ");
+				}
+				System.out.println(")");
+			}else
+				System.out.println("\t- " + dbCol.getADQLName() + " in " + ((dbCol.getTable() == null) ? "<NULL>" : dbCol.getTable().getADQLName()) + " (= " + dbCol.getDBName() + " in " + ((dbCol.getTable() == null) ? "<NULL>" : dbCol.getTable().getDBName()) + ")");
+		}
+		System.out.println();
+
+		/* TEST OF JOIN USING 1 *
+			System.out.println("\n### USING JOIN 2 ###");
+			usingList.clear();
+			usingList.add(new TAPColumn("id"));
+			SearchColumnList joinUsing2 = joinUsing(listA, join3, usingList);
+			
+		// DEBUG
+			for(DBColumn dbCol : joinUsing2){
+				System.out.println("\t- "+dbCol.getADQLName()+" in "+((dbCol.getTable()==null)?"<NULL>":dbCol.getTable().getADQLName())+" (= "+dbCol.getDBName()+" in "+((dbCol.getTable()==null)?"<NULL>":dbCol.getTable().getDBName())+")");
+			}
+			System.out.println();*/
+
+	}
+
+	public static final SearchColumnList join(final SearchColumnList leftList, final SearchColumnList rightList, final boolean natural, final ArrayList<ADQLColumn> usingList) throws UnresolvedJoin{
+
+		SearchColumnList list = new SearchColumnList();
+		/*SearchColumnList leftList = leftTable.getDBColumns();
+		SearchColumnList rightList = rightTable.getDBColumns();*/
+
+		/* 1. Figure out duplicated columns */
+		HashMap<String,DBCommonColumn> mapDuplicated = new HashMap<String,DBCommonColumn>();
+		// CASE: NATURAL
+		if (natural){
+			// Find duplicated items between the two lists and add one common column in mapDuplicated for each
+			DBColumn rightCol;
+			for(DBColumn leftCol : leftList){
+				// search for at most one column with the same name in the RIGHT list
+				// and throw an exception is there are several matches:
+				rightCol = findAtMostOneColumn(leftCol.getADQLName(), (byte)0, rightList, false);
+				// if there is one...
+				if (rightCol != null){
+					// ...check there is only one column with this name in the LEFT list,
+					// and throw an exception if it is not the case:
+					findExactlyOneColumn(leftCol.getADQLName(), (byte)0, leftList, true);
+					// ...create a common column:
+					mapDuplicated.put(leftCol.getADQLName().toLowerCase(), new DBCommonColumn(leftCol, rightCol));
+				}
+			}
+
+		}
+		// CASE: USING
+		else if (usingList != null && !usingList.isEmpty()){
+			// For each columns of usingList, check there is in each list exactly one matching column, and then, add it in mapDuplicated
+			DBColumn leftCol, rightCol;
+			for(ADQLColumn usingCol : usingList){
+				// search for exactly one column with the same name in the LEFT list
+				// and throw an exception if there is none, or if there are several matches:
+				leftCol = findExactlyOneColumn(usingCol.getColumnName(), usingCol.getCaseSensitive(), leftList, true);
+				// idem in the RIGHT list:
+				rightCol = findExactlyOneColumn(usingCol.getColumnName(), usingCol.getCaseSensitive(), rightList, false);
+				// create a common column:
+				mapDuplicated.put((usingCol.isCaseSensitive(IdentifierField.COLUMN) ? ("\"" + usingCol.getColumnName() + "\"") : usingCol.getColumnName().toLowerCase()), new DBCommonColumn(leftCol, rightCol));
+			}
+
+		}
+		// CASE: NO DUPLICATION TO FIGURE OUT
+		else{
+			// Return the union of both lists:
+			list.addAll(leftList);
+			list.addAll(rightList);
+			return list;
+		}
+
+		/* 2. Add all columns of the left list except the ones identified as duplications */
+		addAllExcept(leftList, list, mapDuplicated);
+
+		/* 3. Add all columns of the right list except the ones identified as duplications */
+		addAllExcept(rightList, list, mapDuplicated);
+
+		/* 4. Add all common columns of mapDuplicated */
+		list.addAll(mapDuplicated.values());
+
+		return list;
+
+	}
+
+	public final static void addAllExcept(final SearchColumnList itemsToAdd, final SearchColumnList target, final Map<String,DBCommonColumn> exception){
+		for(DBColumn col : itemsToAdd){
+			if (!exception.containsKey(col.getADQLName().toLowerCase()) && !exception.containsKey("\"" + col.getADQLName() + "\""))
+				target.add(col);
+		}
+	}
+
+	public final static DBColumn findExactlyOneColumn(final String columnName, final byte caseSensitive, final SearchColumnList list, final boolean leftList) throws UnresolvedJoin{
+		DBColumn result = findAtMostOneColumn(columnName, caseSensitive, list, leftList);
+		if (result == null)
+			throw new UnresolvedJoin("Column \"" + columnName + "\" specified in USING clause does not exist in " + (leftList ? "left" : "right") + " table!");
+		else
+			return result;
+	}
+
+	public final static DBColumn findAtMostOneColumn(final String columnName, final byte caseSensitive, final SearchColumnList list, final boolean leftList) throws UnresolvedJoin{
+		ArrayList<DBColumn> result = list.search(null, null, null, columnName, caseSensitive);
+		if (result.isEmpty())
+			return null;
+		else if (result.size() > 1)
+			throw new UnresolvedJoin("Common column name \"" + columnName + "\" appears more than once in " + (leftList ? "left" : "right") + " table!");
+		else
+			return result.get(0);
+	}
+
+	/**
+	 * Tells whether the given column is a common column (that's to say, a unification of several columns of the same name).
+	 * 
+	 * @param col	A DBColumn.
+	 * @return		true if the given column is a common column, false otherwise (particularly if col = null).
+	 */
+	public static final boolean isCommonColumn(final DBColumn col){
+		return (col != null && col instanceof DBCommonColumn);
+	}
+
+}