diff --git a/src/adql/parser/SQLServer_ADQLQueryFactory.java b/src/adql/parser/SQLServer_ADQLQueryFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..18b2b54d66103eff46d86d66d26ac5ba3bfa2661
--- /dev/null
+++ b/src/adql/parser/SQLServer_ADQLQueryFactory.java
@@ -0,0 +1,75 @@
+package adql.parser;
+
+/*
+ * 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 2016 - Astronomisches Rechen Institut (ARI)
+ */
+
+import adql.query.from.ADQLJoin;
+import adql.query.from.CrossJoin;
+import adql.query.from.FromContent;
+import adql.query.from.InnerJoin;
+import adql.query.from.OuterJoin;
+import adql.query.from.SQLServer_InnerJoin;
+import adql.query.from.SQLServer_OuterJoin;
+import adql.query.from.OuterJoin.OuterType;
+import adql.translator.SQLServerTranslator;
+
+/**
+ * <p>Special extension of {@link ADQLQueryFactory} for MS SQL Server.</p>
+ * 
+ * <p><b>Important:</b>
+ * 	This class is generally used when an ADQL translator for MS SQL Server is needed.
+ * 	See {@link SQLServerTranslator} for more details.
+ * </p>
+ * 
+ * <p>
+ * 	The only difference with {@link ADQLQueryFactory} is the creation of an
+ * 	{@link ADQLJoin}. Instead of creating {@link InnerJoin} and {@link OuterJoin},
+ * 	{@link SQLServer_InnerJoin} and {@link SQLServer_OuterJoin} are respectively created.
+ * 	The only difference between these last classes and the first ones is in the processing
+ * 	of NATURAL JOINs and JOINs using the keyword USING.
+ * </p>
+ * 
+ * @author Gr&eacute;gory Mantelet (ARI)
+ * @version 1.4 (03/2016)
+ * @since 1.4
+ * 
+ * @see SQLServer_InnerJoin
+ * @see SQLServer_OuterJoin
+ * @see SQLServerTranslator
+ */
+public class SQLServer_ADQLQueryFactory extends ADQLQueryFactory {
+
+	public ADQLJoin createJoin(JoinType type, FromContent leftTable, FromContent rightTable) throws Exception{
+		switch(type){
+			case CROSS:
+				return new CrossJoin(leftTable, rightTable);
+			case INNER:
+				return new SQLServer_InnerJoin(leftTable, rightTable);
+			case OUTER_LEFT:
+				return new SQLServer_OuterJoin(leftTable, rightTable, OuterType.LEFT);
+			case OUTER_RIGHT:
+				return new SQLServer_OuterJoin(leftTable, rightTable, OuterType.RIGHT);
+			case OUTER_FULL:
+				return new SQLServer_OuterJoin(leftTable, rightTable, OuterType.FULL);
+			default:
+				throw new Exception("Unknown join type: " + type);
+		}
+	}
+	
+}
\ No newline at end of file
diff --git a/src/adql/query/from/SQLServer_InnerJoin.java b/src/adql/query/from/SQLServer_InnerJoin.java
new file mode 100644
index 0000000000000000000000000000000000000000..432e1ffcc1fc085a0788ec8d4f4505536673430f
--- /dev/null
+++ b/src/adql/query/from/SQLServer_InnerJoin.java
@@ -0,0 +1,206 @@
+package adql.query.from;
+
+/*
+ * 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 2016 - Astronomisches Rechen Institut (ARI)
+ */
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import adql.db.DBColumn;
+import adql.db.DBCommonColumn;
+import adql.db.SearchColumnList;
+import adql.db.exception.UnresolvedJoinException;
+import adql.parser.SQLServer_ADQLQueryFactory;
+import adql.query.ClauseConstraints;
+import adql.query.IdentifierField;
+import adql.query.operand.ADQLColumn;
+
+/**
+ * <p>Special implementation of {@link InnerJoin} for MS SQL Server.</p>
+ * 
+ * <p><b>Important:</b>
+ * 	Instances of this class are created only by {@link SQLServer_ADQLQueryFactory}.
+ * </p>
+ * 
+ * <p>
+ * 	This implementation just changes the behavior the {@link #getDBColumns()}.
+ * 	In MS SQL Server, there is no keyword NATURAL and USING. That's why the {@link DBColumn}s
+ * 	returned by {@link DBColumn} can not contain any {@link DBCommonColumn}. Instead,
+ * 	the {@link DBColumn} of the first joined table (i.e. the left one) is returned.
+ * </p>
+ * 
+ * <p>
+ * 	Since this special behavior is also valid for {@link OuterJoin}, a special implementation
+ * 	of this class has been also created: {@link SQLServer_OuterJoin}. Both must have exactly the
+ * 	same behavior when {@link #getDBColumns()} is called. That's why the static function
+ * 	{@link #getDBColumns(ADQLJoin)} has been created. It is called by {@link SQLServer_InnerJoin}
+ * 	and {@link SQLServer_OuterJoin}.
+ * </p>
+ * 
+ * @author Gr&eacute;gory Mantelet (ARI)
+ * @version 1.4 (03/2016)
+ * @since 1.4
+ * 
+ * @see SQLServer_ADQLQueryFactory
+ */
+public class SQLServer_InnerJoin extends InnerJoin {
+
+	/**
+	 * Builds a NATURAL INNER JOIN between the two given "tables".
+	 * 
+	 * @param left	Left "table".
+	 * @param right	Right "table".
+	 * 
+	 * @see InnerJoin#InnerJoin(FromContent, FromContent)
+	 */
+	public SQLServer_InnerJoin(FromContent left, FromContent right) {
+		super(left, right);
+	}
+
+	/**
+	 * Builds an INNER JOIN between the two given "tables" with the given condition.
+	 * 
+	 * @param left		Left "table".
+	 * @param right		Right "table".
+	 * @param condition	Join condition.
+	 * 
+	 * @see InnerJoin#InnerJoin(FromContent, FromContent, ClauseConstraints)
+	 */
+	public SQLServer_InnerJoin(FromContent left, FromContent right, ClauseConstraints condition) {
+		super(left, right, condition);
+	}
+
+	/**
+	 * Builds an INNER JOIN between the two given "tables" with the given condition.
+	 * 
+	 * @param left		Left "table".
+	 * @param right		Right "table".
+	 * @param condition	Join condition.
+	 * 
+	 * @see InnerJoin#InnerJoin(FromContent, FromContent, Collection)
+	 */
+	public SQLServer_InnerJoin(FromContent left, FromContent right, Collection<ADQLColumn> lstColumns) {
+		super(left, right, lstColumns);
+	}
+
+	/**
+	 * Builds a copy of the given INNER join.
+	 * 
+	 * @param toCopy		The INNER join to copy.
+	 * 
+	 * @throws Exception	If there is an error during the copy.
+	 * 
+	 * @see InnerJoin#InnerJoin(InnerJoin)
+	 */
+	public SQLServer_InnerJoin(InnerJoin toCopy) throws Exception {
+		super(toCopy);
+	}
+
+	@Override
+	public SearchColumnList getDBColumns() throws UnresolvedJoinException {
+		return getDBColumns(this);
+	}
+
+	/**
+	 * <p>Gets the list of all columns (~ database metadata) available in this FROM part.
+	 * Columns implied in a NATURAL join or in a USING list, are not returned as a {@link DBCommonColumn} ; 
+	 * actually, just the corresponding {@link DBColumn} of the left table is returned.</p>
+	 * 
+	 * @return	All the available {@link DBColumn}s.
+	 * @throws UnresolvedJoinException If a join is not possible.
+	 */
+	public static SearchColumnList getDBColumns(final ADQLJoin join) throws UnresolvedJoinException{
+		try{
+			SearchColumnList list = new SearchColumnList();
+			SearchColumnList leftList = join.getLeftTable().getDBColumns();
+			SearchColumnList rightList = join.getRightTable().getDBColumns();
+
+			/* 1. Figure out duplicated columns */
+			HashMap<String,DBColumn> mapDuplicated = new HashMap<String,DBColumn>();
+			// CASE: NATURAL
+			if (join.isNatural()){
+				// 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);
+						// ...add the left column:
+						mapDuplicated.put(leftCol.getADQLName().toLowerCase(), leftCol);
+					}
+				}
+
+			}
+			// CASE: USING
+			else if (join.hasJoinedColumns()){
+				// For each columns of usingList, check there is in each list exactly one matching column, and then, add it in mapDuplicated
+				DBColumn leftCol;
+				ADQLColumn usingCol;
+				Iterator<ADQLColumn> itCols = join.getJoinedColumns();
+				while(itCols.hasNext()){
+					usingCol = itCols.next();
+					// 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:
+					findExactlyOneColumn(usingCol.getColumnName(), usingCol.getCaseSensitive(), rightList, false);
+					// add the left column:
+					mapDuplicated.put((usingCol.isCaseSensitive(IdentifierField.COLUMN) ? ("\"" + usingCol.getColumnName() + "\"") : usingCol.getColumnName().toLowerCase()), leftCol);
+				}
+
+			}
+			// 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 */
+			addAllExcept2(leftList, list, mapDuplicated);
+
+			/* 3. Add all columns of the right list except the ones identified as duplications */
+			addAllExcept2(rightList, list, mapDuplicated);
+
+			/* 4. Add all common columns of mapDuplicated */
+			list.addAll(0, mapDuplicated.values());
+
+			return list;
+		}catch(UnresolvedJoinException uje){
+			uje.setPosition(join.getPosition());
+			throw uje;
+		}
+	}
+	
+	public final static void addAllExcept2(final SearchColumnList itemsToAdd, final SearchColumnList target, final Map<String,DBColumn> exception){
+		for(DBColumn col : itemsToAdd){
+			if (!exception.containsKey(col.getADQLName().toLowerCase()) && !exception.containsKey("\"" + col.getADQLName() + "\""))
+				target.add(col);
+		}
+	}
+
+}
diff --git a/src/adql/query/from/SQLServer_OuterJoin.java b/src/adql/query/from/SQLServer_OuterJoin.java
new file mode 100644
index 0000000000000000000000000000000000000000..8f24e49fbb6719e11634e9c26d53c8826dff8d08
--- /dev/null
+++ b/src/adql/query/from/SQLServer_OuterJoin.java
@@ -0,0 +1,122 @@
+package adql.query.from;
+
+/*
+ * 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 2016 - Astronomisches Rechen Institut (ARI)
+ */
+
+import java.util.Collection;
+
+import adql.db.DBColumn;
+import adql.db.DBCommonColumn;
+import adql.db.SearchColumnList;
+import adql.db.exception.UnresolvedJoinException;
+import adql.parser.SQLServer_ADQLQueryFactory;
+import adql.query.ClauseConstraints;
+import adql.query.operand.ADQLColumn;
+
+/**
+ * <p>Special implementation of {@link OuterJoin} for MS SQL Server.</p>
+ * 
+ * <p><b>Important:</b>
+ * 	Instances of this class are created only by {@link SQLServer_ADQLQueryFactory}.
+ * </p>
+ * 
+ * <p>
+ * 	This implementation just changes the behavior the {@link #getDBColumns()}.
+ * 	In MS SQL Server, there is no keyword NATURAL and USING. That's why the {@link DBColumn}s
+ * 	returned by {@link DBColumn} can not contain any {@link DBCommonColumn}. Instead,
+ * 	the {@link DBColumn} of the first joined table (i.e. the left one) is returned.
+ * </p>
+ * 
+ * <p>
+ * 	Since this special behavior is also valid for {@link InnerJoin}, a special implementation
+ * 	of this class has been also created: {@link SQLServer_InnerJoin}. Both must have exactly the
+ * 	same behavior when {@link #getDBColumns()} is called. That's why the static function
+ * 	{@link InnerJoin#getDBColumns(ADQLJoin)} has been created. It is called by {@link SQLServer_InnerJoin}
+ * 	and {@link SQLServer_OuterJoin}.
+ * </p>
+ * 
+ * @author Gr&eacute;gory Mantelet (ARI)
+ * @version 1.4 (03/2016)
+ * @since 1.4
+ * 
+ * @see SQLServer_ADQLQueryFactory
+ * @see SQLServer_InnerJoin
+ */
+public class SQLServer_OuterJoin extends OuterJoin {
+
+	/**
+	 * Builds a NATURAL OUTER join between the two given "tables".
+	 * 
+	 * @param left	Left "table".
+	 * @param right	Right "table".
+	 * @param type	OUTER join type (left, right or full).
+	 * 
+	 * @see OuterJoin#OuterJoin(FromContent, FromContent, OuterType)
+	 */
+	public SQLServer_OuterJoin(FromContent left, FromContent right, OuterType type) {
+		super(left, right, type);
+	}
+
+	/**
+	 * Builds an OUTER join between the two given "tables" with the given condition.
+	 * 
+	 * @param left		Left "table".
+	 * @param right		Right "table".
+	 * @param type		Outer join type (left, right or full).
+	 * @param condition	Join condition.
+	 * 
+	 * @see OuterJoin#OuterJoin(FromContent, FromContent, OuterType, ClauseConstraints)
+	 */
+	public SQLServer_OuterJoin(FromContent left, FromContent right, OuterType type, ClauseConstraints condition) {
+		super(left, right, type, condition);
+	}
+
+	/**
+	 * Builds an OUTER join between the two given "tables" with a list of columns to join.
+	 * 
+	 * @param left			Left "table".
+	 * @param right			Right "table".
+	 * @param type			Outer join type.
+	 * @param lstColumns	List of columns to join.
+	 * 
+	 * @see OuterJoin#OuterJoin(FromContent, FromContent, OuterType, Collection)
+	 */
+	public SQLServer_OuterJoin(FromContent left, FromContent right, OuterType type, Collection<ADQLColumn> lstColumns) {
+		super(left, right, type, lstColumns);
+	}
+
+	/**
+	 * Builds a copy of the given OUTER join.
+	 * 
+	 * @param toCopy		The OUTER join to copy.
+	 * 
+	 * @throws Exception	If there is an error during the copy.
+	 * 
+	 * @see OuterJoin#OuterJoin(OuterJoin)
+	 */
+	public SQLServer_OuterJoin(OuterJoin toCopy) throws Exception {
+		super(toCopy);
+	}
+
+	@Override
+	public SearchColumnList getDBColumns() throws UnresolvedJoinException{
+		return SQLServer_InnerJoin.getDBColumns(this);
+	}
+
+}
diff --git a/src/adql/translator/SQLServerTranslator.java b/src/adql/translator/SQLServerTranslator.java
new file mode 100644
index 0000000000000000000000000000000000000000..775f1b40aa343cbc4342a951cb2b238e702d6f34
--- /dev/null
+++ b/src/adql/translator/SQLServerTranslator.java
@@ -0,0 +1,398 @@
+package adql.translator;
+
+/*
+ * 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 2016 - Astronomisches Rechen Institut (ARI)
+ */
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+import adql.db.DBChecker;
+import adql.db.DBColumn;
+import adql.db.DBTable;
+import adql.db.DBType;
+import adql.db.DefaultDBColumn;
+import adql.db.DefaultDBTable;
+import adql.db.SearchColumnList;
+import adql.db.DBType.DBDatatype;
+import adql.db.STCS.Region;
+import adql.db.exception.UnresolvedJoinException;
+import adql.parser.ADQLParser;
+import adql.parser.ParseException;
+import adql.parser.SQLServer_ADQLQueryFactory;
+import adql.query.ADQLQuery;
+import adql.query.IdentifierField;
+import adql.query.from.ADQLJoin;
+import adql.query.operand.ADQLColumn;
+import adql.query.operand.function.geometry.AreaFunction;
+import adql.query.operand.function.geometry.BoxFunction;
+import adql.query.operand.function.geometry.CentroidFunction;
+import adql.query.operand.function.geometry.CircleFunction;
+import adql.query.operand.function.geometry.ContainsFunction;
+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.IntersectsFunction;
+import adql.query.operand.function.geometry.PointFunction;
+import adql.query.operand.function.geometry.PolygonFunction;
+import adql.query.operand.function.geometry.RegionFunction;
+
+/**
+ * <p>MS SQL Server translator.</p>
+ * 
+ * <p><b>Important:</b>
+ * 	This translator works correctly ONLY IF {@link SQLServer_ADQLQueryFactory} has been used
+ * 	to create any ADQL query this translator is asked to translate.
+ * </p>
+ * 
+ * TODO See how case sensitivity is supported by MS SQL Server and modify this translator accordingly.
+ * 
+ * TODO Extend this class for each MS SQL Server extension supporting geometry and particularly
+ *      {@link #translateGeometryFromDB(Object)}, {@link #translateGeometryToDB(Region)} and all this other
+ *      translate(...) functions for the ADQL's geometrical functions.
+ * 
+ * TODO Check MS SQL Server datatypes (see {@link #convertTypeFromDB(int, String, String, String[])},
+ *      {@link #convertTypeToDB(DBType)}).
+ * 
+ * <p><i><b>Important note:</b>
+ * 	Geometrical functions are not translated ; the translation returned for them is their ADQL expression.
+ * </i></p>
+ * 
+ * @author Gr&eacute;gory Mantelet (ARI)
+ * @version 1.4 (03/2016)
+ * @since 1.4
+ * 
+ * @see SQLServer_ADQLQueryFactory
+ */
+public class SQLServerTranslator extends JDBCTranslator {
+	
+	/* TODO Temporary MAIN function.
+	 *      TO REMOVE for the release. */
+	public final static void main(final String[] args) throws Exception {
+		final String adqlquery = "SELECT id, name, aColumn, anotherColumn FROM aTable A NATURAL JOIN anotherTable B;";
+		System.out.println("ADQL Query:\n"+adqlquery);
+		
+		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);
+		
+		ADQLQuery query = (new ADQLParser(new DBChecker(tables), new SQLServer_ADQLQueryFactory())).parseQuery(adqlquery);
+		
+		SQLServerTranslator translator = new SQLServerTranslator();
+		System.out.println("\nIn MS SQL Server:\n"+translator.translate(query));
+	}
+
+	/** <p>Indicate the case sensitivity to apply to each SQL identifier (only SCHEMA, TABLE and COLUMN).</p>
+	 * 
+	 * <p><i>Note:
+	 * 	In this implementation, this field is set by the constructor and never modified elsewhere.
+	 * 	It would be better to never modify it after the construction in order to keep a certain consistency.
+	 * </i></p>
+	 */
+	protected byte caseSensitivity = 0x00;
+
+	/**
+	 * Builds an SQLServerTranslator which always translates in SQL all identifiers (schema, table and column) in a case sensitive manner ;
+	 * in other words, schema, table and column names will be surrounded by double quotes in the SQL translation.
+	 */
+	public SQLServerTranslator(){
+		caseSensitivity = 0x0F;
+	}
+
+	/**
+	 * Builds an SQLServerTranslator which always translates in SQL all identifiers (schema, table and column) in the specified case sensitivity ;
+	 * in other words, schema, table and column names will all be surrounded or not by double quotes in the SQL translation.
+	 * 
+	 * @param allCaseSensitive	<i>true</i> to translate all identifiers in a case sensitive manner (surrounded by double quotes), <i>false</i> for case insensitivity. 
+	 */
+	public SQLServerTranslator(final boolean allCaseSensitive){
+		caseSensitivity = allCaseSensitive ? (byte)0x0F : (byte)0x00;
+	}
+
+	/**
+	 * Builds an SQLServerTranslator which will always translate in SQL identifiers with the defined case sensitivity.
+	 * 
+	 * @param catalog	<i>true</i> to translate catalog names with double quotes (case sensitive in the DBMS), <i>false</i> otherwise.
+	 * @param schema	<i>true</i> to translate schema names with double quotes (case sensitive in the DBMS), <i>false</i> otherwise.
+	 * @param table		<i>true</i> to translate table names with double quotes (case sensitive in the DBMS), <i>false</i> otherwise.
+	 * @param column	<i>true</i> to translate column names with double quotes (case sensitive in the DBMS), <i>false</i> otherwise.
+	 */
+	public SQLServerTranslator(final boolean catalog, final boolean schema, final boolean table, final boolean column){
+		caseSensitivity = IdentifierField.CATALOG.setCaseSensitive(caseSensitivity, catalog);
+		caseSensitivity = IdentifierField.SCHEMA.setCaseSensitive(caseSensitivity, schema);
+		caseSensitivity = IdentifierField.TABLE.setCaseSensitive(caseSensitivity, table);
+		caseSensitivity = IdentifierField.COLUMN.setCaseSensitive(caseSensitivity, column);
+	}
+
+	@Override
+	public boolean isCaseSensitive(final IdentifierField field) {
+		return field == null ? false : field.isCaseSensitive(caseSensitivity);
+	}
+
+	@Override
+	public String translate(final ADQLJoin join) throws TranslationException {
+		StringBuffer sql = new StringBuffer(translate(join.getLeftTable()));
+
+		sql.append(' ').append(join.getJoinType()).append(' ').append(translate(join.getRightTable())).append(' ');
+
+		// CASE: NATURAL
+		if (join.isNatural()){
+			try{
+				StringBuffer buf = new StringBuffer();
+			
+				// Find duplicated items between the two lists and translate them as ON conditions:
+				DBColumn rightCol;
+				SearchColumnList leftList = join.getLeftTable().getDBColumns();
+				SearchColumnList rightList = join.getRightTable().getDBColumns();
+				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 = ADQLJoin.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:
+						ADQLJoin.findExactlyOneColumn(leftCol.getADQLName(), (byte)0, leftList, true);
+						// ...append the corresponding join condition:
+						if (buf.length() > 0)
+							buf.append(" AND ");
+						buf.append(getQualifiedTableName(leftCol.getTable())).append('.').append(getColumnName(leftCol));
+						buf.append("=");
+						buf.append(getQualifiedTableName(rightCol.getTable())).append('.').append(getColumnName(rightCol));
+					}
+				}
+	
+				sql.append("ON ").append(buf.toString());
+			}catch(UnresolvedJoinException uje){
+				throw new TranslationException("Impossible to resolve the NATURAL JOIN between "+join.getLeftTable().toADQL()+" and "+join.getRightTable().toADQL()+"!", uje);
+			}
+		}
+		// CASE: USING
+		else if (join.hasJoinedColumns()){
+			try{
+				StringBuffer buf = new StringBuffer();
+				
+				// For each columns of usingList, check there is in each list exactly one matching column, and then, translate it as ON condition:
+				DBColumn leftCol, rightCol;
+				ADQLColumn usingCol;
+				SearchColumnList leftList = join.getLeftTable().getDBColumns();
+				SearchColumnList rightList = join.getRightTable().getDBColumns();
+				Iterator<ADQLColumn> itCols = join.getJoinedColumns();
+				while(itCols.hasNext()){
+					usingCol = itCols.next();
+					// 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 = ADQLJoin.findExactlyOneColumn(usingCol.getColumnName(), usingCol.getCaseSensitive(), leftList, true);
+					// idem in the RIGHT list:
+					rightCol = ADQLJoin.findExactlyOneColumn(usingCol.getColumnName(), usingCol.getCaseSensitive(), rightList, false);
+					// append the corresponding join condition:
+					if (buf.length() > 0)
+						buf.append(" AND ");
+					buf.append(getQualifiedTableName(leftCol.getTable())).append('.').append(getColumnName(leftCol));
+					buf.append("=");
+					buf.append(getQualifiedTableName(rightCol.getTable())).append('.').append(getColumnName(rightCol));
+				}
+				
+				sql.append("ON ").append(buf.toString());
+			}catch(UnresolvedJoinException uje){
+				throw new TranslationException("Impossible to resolve the JOIN USING between "+join.getLeftTable().toADQL()+" and "+join.getRightTable().toADQL()+"!", uje);
+			}
+		}
+		// DEFAULT CASE:
+		else
+			sql.append(translate(join.getJoinCondition()));
+
+		return sql.toString();
+	}
+	
+	@Override
+	public String translate(final ExtractCoord extractCoord) throws TranslationException {
+		return getDefaultADQLFunction(extractCoord);
+	}
+
+	@Override
+	public String translate(final ExtractCoordSys extractCoordSys) throws TranslationException {
+		return getDefaultADQLFunction(extractCoordSys);
+	}
+
+	@Override
+	public String translate(final AreaFunction areaFunction) throws TranslationException {
+		return getDefaultADQLFunction(areaFunction);
+	}
+
+	@Override
+	public String translate(final CentroidFunction centroidFunction) throws TranslationException {
+		return getDefaultADQLFunction(centroidFunction);
+	}
+
+	@Override
+	public String translate(final DistanceFunction fct) throws TranslationException {
+		return getDefaultADQLFunction(fct);
+	}
+
+	@Override
+	public String translate(final ContainsFunction fct) throws TranslationException {
+		return getDefaultADQLFunction(fct);
+	}
+
+	@Override
+	public String translate(final IntersectsFunction fct) throws TranslationException {
+		return getDefaultADQLFunction(fct);
+	}
+
+	@Override
+	public String translate(final PointFunction point) throws TranslationException {
+		return getDefaultADQLFunction(point);
+	}
+
+	@Override
+	public String translate(final CircleFunction circle) throws TranslationException {
+		return getDefaultADQLFunction(circle);
+	}
+
+	@Override
+	public String translate(final BoxFunction box) throws TranslationException {
+		return getDefaultADQLFunction(box);
+	}
+
+	@Override
+	public String translate(final PolygonFunction polygon) throws TranslationException {
+		return getDefaultADQLFunction(polygon);
+	}
+
+	@Override
+	public String translate(final RegionFunction region) throws TranslationException {
+		return getDefaultADQLFunction(region);
+	}
+
+	@Override
+	public DBType convertTypeFromDB(final int dbmsType, final String rawDbmsTypeName, String dbmsTypeName, final String[] params){
+		// If no type is provided return VARCHAR:
+		if (dbmsTypeName == null || dbmsTypeName.trim().length() == 0)
+			return null;
+
+		// Put the dbmsTypeName in lower case for the following comparisons:
+		dbmsTypeName = dbmsTypeName.toLowerCase();
+
+		// Extract the length parameter (always the first one):
+		int lengthParam = DBType.NO_LENGTH;
+		if (params != null && params.length > 0){
+			try{
+				lengthParam = Integer.parseInt(params[0]);
+			}catch(NumberFormatException nfe){}
+		}
+
+		// SMALLINT
+		if (dbmsTypeName.equals("smallint") || dbmsTypeName.equals("tinyint") || dbmsTypeName.equals("bit"))
+			return new DBType(DBDatatype.SMALLINT);
+		// INTEGER
+		else if (dbmsTypeName.equals("int"))
+			return new DBType(DBDatatype.INTEGER);
+		// BIGINT
+		else if (dbmsTypeName.equals("bigint"))
+			return new DBType(DBDatatype.BIGINT);
+		// REAL (cf https://msdn.microsoft.com/fr-fr/library/ms173773(v=sql.120).aspx)
+		else if (dbmsTypeName.equals("real") || (dbmsTypeName.equals("float") && lengthParam >= 1 && lengthParam <= 24))
+			return new DBType(DBDatatype.REAL);
+		// DOUBLE (cf https://msdn.microsoft.com/fr-fr/library/ms173773(v=sql.120).aspx)
+		else if (dbmsTypeName.equals("float") || dbmsTypeName.equals("decimal") || dbmsTypeName.equals("numeric"))
+			return new DBType(DBDatatype.DOUBLE);
+		// BINARY
+		else if (dbmsTypeName.equals("binary"))
+			return new DBType(DBDatatype.BINARY, lengthParam);
+		// VARBINARY
+		else if (dbmsTypeName.equals("varbinary"))
+			return new DBType(DBDatatype.VARBINARY, lengthParam);
+		// CHAR
+		else if (dbmsTypeName.equals("char") || dbmsTypeName.equals("nchar"))
+			return new DBType(DBDatatype.CHAR, lengthParam);
+		// VARCHAR
+		else if (dbmsTypeName.equals("varchar") || dbmsTypeName.equals("nvarchar"))
+			return new DBType(DBDatatype.VARCHAR, lengthParam);
+		// BLOB
+		else if (dbmsTypeName.equals("image"))
+			return new DBType(DBDatatype.BLOB);
+		// CLOB
+		else if (dbmsTypeName.equals("text") || dbmsTypeName.equals("ntext"))
+			return new DBType(DBDatatype.CLOB);
+		// TIMESTAMP
+		else if (dbmsTypeName.equals("timestamp") || dbmsTypeName.equals("datetime") || dbmsTypeName.equals("datetime2") || dbmsTypeName.equals("datetimeoffset") || dbmsTypeName.equals("smalldatetime") || dbmsTypeName.equals("time") || dbmsTypeName.equals("date") || dbmsTypeName.equals("date"))
+			return new DBType(DBDatatype.TIMESTAMP);
+		// Default:
+		else
+			return null;
+	}
+
+	@Override
+	public String convertTypeToDB(final DBType type){
+		if (type == null)
+			return "varchar";
+
+		switch(type.type){
+
+			case SMALLINT:
+			case REAL:
+			case BIGINT:
+			case CHAR:
+			case VARCHAR:
+			case BINARY:
+			case VARBINARY:
+				return type.type.toString().toLowerCase();
+
+			case INTEGER:
+				return "int";
+
+			// (cf https://msdn.microsoft.com/fr-fr/library/ms173773(v=sql.120).aspx)
+			case DOUBLE:
+				return "float(53)";
+
+			case TIMESTAMP:
+				return "datetime";
+
+			case BLOB:
+				return "image";
+
+			case CLOB:
+				return "text";
+
+			case POINT:
+			case REGION:
+			default:
+				return "varchar";
+		}
+	}
+
+	@Override
+	public Region translateGeometryFromDB(final Object jdbcColValue) throws ParseException{
+		throw new ParseException("Unsupported geometrical value! The value \"" + jdbcColValue + "\" can not be parsed as a region.");
+	}
+
+	@Override
+	public Object translateGeometryToDB(final Region region) throws ParseException{
+		throw new ParseException("Geometries can not be uploaded in the database in this implementation!");
+	}
+
+}
diff --git a/test/adql/query/from/TestSQLServer_InnerJoin.java b/test/adql/query/from/TestSQLServer_InnerJoin.java
new file mode 100644
index 0000000000000000000000000000000000000000..70be938cad22480f5fe969dc1bfc166c602d45ed
--- /dev/null
+++ b/test/adql/query/from/TestSQLServer_InnerJoin.java
@@ -0,0 +1,159 @@
+package adql.query.from;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import adql.db.DBColumn;
+import adql.db.DBCommonColumn;
+import adql.db.DBType;
+import adql.db.DBType.DBDatatype;
+import adql.db.DefaultDBColumn;
+import adql.db.DefaultDBTable;
+import adql.db.SearchColumnList;
+import adql.query.IdentifierField;
+import adql.query.operand.ADQLColumn;
+
+public class TestSQLServer_InnerJoin {
+
+	private ADQLTable tableA, tableB, tableC;
+
+	@Before
+	public void setUp() throws Exception{
+		/* SET THE TABLES AND COLUMNS NEEDED FOR THE TEST */
+		// Describe the available table:
+		DefaultDBTable metaTableA = new DefaultDBTable("A");
+		metaTableA.setADQLSchemaName("public");
+		DefaultDBTable metaTableB = new DefaultDBTable("B");
+		metaTableB.setADQLSchemaName("public");
+		DefaultDBTable metaTableC = new DefaultDBTable("C");
+		metaTableC.setADQLSchemaName("public");
+
+		// Describe its columns:
+		metaTableA.addColumn(new DefaultDBColumn("id", new DBType(DBDatatype.VARCHAR), metaTableA));
+		metaTableA.addColumn(new DefaultDBColumn("txta", new DBType(DBDatatype.VARCHAR), metaTableA));
+		metaTableB.addColumn(new DefaultDBColumn("id", new DBType(DBDatatype.VARCHAR), metaTableB));
+		metaTableB.addColumn(new DefaultDBColumn("txtb", new DBType(DBDatatype.VARCHAR), metaTableB));
+		metaTableC.addColumn(new DefaultDBColumn("Id", new DBType(DBDatatype.VARCHAR), metaTableC));
+		metaTableC.addColumn(new DefaultDBColumn("txta", new DBType(DBDatatype.VARCHAR), metaTableC));
+		metaTableC.addColumn(new DefaultDBColumn("txtc", new DBType(DBDatatype.VARCHAR), metaTableC));
+
+		// Build the ADQL tables:
+		tableA = new ADQLTable("A");
+		tableA.setDBLink(metaTableA);
+		tableB = new ADQLTable("B");
+		tableB.setDBLink(metaTableB);
+		tableC = new ADQLTable("C");
+		tableC.setDBLink(metaTableC);
+	}
+
+	@Test
+	public void testGetDBColumns(){
+		// Test NATURAL JOIN 1:
+		try{
+			ADQLJoin join = new SQLServer_InnerJoin(tableA, tableB);
+			SearchColumnList joinColumns = join.getDBColumns();
+			assertEquals(3, joinColumns.size());
+			List<DBColumn> lstFound = joinColumns.search(null, null, null, "id", IdentifierField.getFullCaseSensitive(true));
+			assertEquals(1, lstFound.size());
+			assertNotEquals(DBCommonColumn.class, lstFound.get(0).getClass());
+			assertEquals(1, joinColumns.search(null, "public", "A", "id", IdentifierField.getFullCaseSensitive(true)).size());
+			assertEquals(0, joinColumns.search(null, "public", "B", "id", IdentifierField.getFullCaseSensitive(true)).size());
+			assertEquals(0, joinColumns.search(null, "public", "C", "id", IdentifierField.getFullCaseSensitive(true)).size());
+			lstFound = joinColumns.search(null, "public", "A", "txta", IdentifierField.getFullCaseSensitive(true));
+			assertEquals(1, lstFound.size());
+			lstFound = joinColumns.search(null, "public", "B", "txtb", IdentifierField.getFullCaseSensitive(true));
+			assertEquals(1, lstFound.size());
+		}catch(Exception ex){
+			ex.printStackTrace();
+			fail("This test should have succeeded!");
+		}
+
+		// Test NATURAL JOIN 2:
+		try{
+			ADQLJoin join = new SQLServer_InnerJoin(tableA, tableC);
+			SearchColumnList joinColumns = join.getDBColumns();
+			assertEquals(3, joinColumns.size());
+
+			// check id (only the column of table A should be found):
+			List<DBColumn> lstFound = joinColumns.search(null, null, null, "id", IdentifierField.getFullCaseSensitive(true));
+			assertEquals(1, lstFound.size());
+			assertNotEquals(DBCommonColumn.class, lstFound.get(0).getClass());
+			assertEquals(1, joinColumns.search(null, "public", "A", "id", IdentifierField.getFullCaseSensitive(true)).size());
+			assertEquals(0, joinColumns.search(null, "public", "C", "id", IdentifierField.getFullCaseSensitive(true)).size());
+			assertEquals(0, joinColumns.search(null, "public", "B", "id", IdentifierField.getFullCaseSensitive(true)).size());
+
+			// check txta (only the column of table A should be found):
+			lstFound = joinColumns.search(null, null, null, "txta", IdentifierField.getFullCaseSensitive(true));
+			assertEquals(1, lstFound.size());
+			assertNotEquals(DBCommonColumn.class, lstFound.get(0).getClass());
+			assertEquals(1, joinColumns.search(null, "public", "A", "txta", IdentifierField.getFullCaseSensitive(true)).size());
+			assertEquals(0, joinColumns.search(null, "public", "C", "txta", IdentifierField.getFullCaseSensitive(true)).size());
+			assertEquals(0, joinColumns.search(null, "public", "B", "id", IdentifierField.getFullCaseSensitive(true)).size());
+
+			// check txtc (only for table C)
+			lstFound = joinColumns.search(null, null, null, "txtc", IdentifierField.getFullCaseSensitive(true));
+			assertEquals(1, lstFound.size());
+			assertNotNull(lstFound.get(0).getTable());
+			assertEquals("C", lstFound.get(0).getTable().getADQLName());
+			assertEquals("public", lstFound.get(0).getTable().getADQLSchemaName());
+
+		}catch(Exception ex){
+			ex.printStackTrace();
+			fail("This test should have succeeded!");
+		}
+
+		// Test with a USING("id"):
+		try{
+			List<ADQLColumn> usingList = new ArrayList<ADQLColumn>(1);
+			usingList.add(new ADQLColumn("id"));
+			ADQLJoin join = new SQLServer_InnerJoin(tableA, tableC, usingList);
+			SearchColumnList joinColumns = join.getDBColumns();
+			assertEquals(4, joinColumns.size());
+
+			// check id (only the column of table A should be found):
+			List<DBColumn> lstFound = joinColumns.search(null, null, null, "id", IdentifierField.getFullCaseSensitive(true));
+			assertEquals(1, lstFound.size());
+			assertNotEquals(DBCommonColumn.class, lstFound.get(0).getClass());
+			assertEquals(1, joinColumns.search(null, "public", "A", "id", IdentifierField.getFullCaseSensitive(true)).size());
+			assertEquals(0, joinColumns.search(null, "public", "C", "id", IdentifierField.getFullCaseSensitive(true)).size());
+			assertEquals(0, joinColumns.search(null, "public", "B", "id", IdentifierField.getFullCaseSensitive(true)).size());
+
+			// check A.txta and C.txta:
+			lstFound = joinColumns.search(null, null, null, "txta", IdentifierField.getFullCaseSensitive(true));
+			assertEquals(2, lstFound.size());
+			// A.txta
+			assertNotNull(lstFound.get(0).getTable());
+			assertEquals("A", lstFound.get(0).getTable().getADQLName());
+			assertEquals("public", lstFound.get(0).getTable().getADQLSchemaName());
+			assertEquals(1, joinColumns.search(null, "public", "A", "txta", IdentifierField.getFullCaseSensitive(true)).size());
+			// C.txta
+			assertNotNull(lstFound.get(1).getTable());
+			assertEquals("C", lstFound.get(1).getTable().getADQLName());
+			assertEquals("public", lstFound.get(1).getTable().getADQLSchemaName());
+			assertEquals(1, joinColumns.search(null, "public", "C", "txta", IdentifierField.getFullCaseSensitive(true)).size());
+			assertEquals(0, joinColumns.search(null, "public", "B", "txta", IdentifierField.getFullCaseSensitive(true)).size());
+
+			// check txtc (only for table C):
+			lstFound = joinColumns.search(null, null, null, "txtc", IdentifierField.getFullCaseSensitive(true));
+			assertEquals(1, lstFound.size());
+			assertNotNull(lstFound.get(0).getTable());
+			assertEquals("C", lstFound.get(0).getTable().getADQLName());
+			assertEquals("public", lstFound.get(0).getTable().getADQLSchemaName());
+			assertEquals(1, joinColumns.search(null, "public", "C", "txtc", IdentifierField.getFullCaseSensitive(true)).size());
+			assertEquals(0, joinColumns.search(null, "public", "A", "txtc", IdentifierField.getFullCaseSensitive(true)).size());
+
+		}catch(Exception ex){
+			ex.printStackTrace();
+			fail("This test should have succeeded!");
+		}
+	}
+
+}
diff --git a/test/adql/translator/TestSQLServerTranslator.java b/test/adql/translator/TestSQLServerTranslator.java
new file mode 100644
index 0000000000000000000000000000000000000000..d3a0290ed0707900324ee13a0f62261819f9c2dd
--- /dev/null
+++ b/test/adql/translator/TestSQLServerTranslator.java
@@ -0,0 +1,85 @@
+package adql.translator;
+
+import static org.junit.Assert.*;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import adql.db.DBChecker;
+import adql.db.DBTable;
+import adql.db.DefaultDBColumn;
+import adql.db.DefaultDBTable;
+import adql.parser.ADQLParser;
+import adql.parser.ParseException;
+import adql.parser.SQLServer_ADQLQueryFactory;
+import adql.query.ADQLQuery;
+
+public class TestSQLServerTranslator {
+	
+	private List<DBTable> tables = null;
+
+	@Before
+	public void setUp() throws Exception {
+		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);
+	}
+
+	@Test
+	public void testNaturalJoin() {
+		final String adqlquery = "SELECT id, name, aColumn, anotherColumn FROM aTable A NATURAL JOIN anotherTable B;";
+		
+		try{
+			ADQLQuery query = (new ADQLParser(new DBChecker(tables), new SQLServer_ADQLQueryFactory())).parseQuery(adqlquery);
+			SQLServerTranslator translator = new SQLServerTranslator();
+			
+			// Test the FROM part:
+			assertEquals("\"aTable\" AS A INNER JOIN \"anotherTable\" AS B ON \"aTable\".\"id\"=\"anotherTable\".\"id\" AND \"aTable\".\"name\"=\"anotherTable\".\"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()));
+			
+		}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() {
+		final String adqlquery = "SELECT B.id, name, aColumn, anotherColumn FROM aTable A JOIN anotherTable B USING(name);";
+		
+		try{
+			ADQLQuery query = (new ADQLParser(new DBChecker(tables), new SQLServer_ADQLQueryFactory())).parseQuery(adqlquery);
+			SQLServerTranslator translator = new SQLServerTranslator();
+			
+			// Test the FROM part:
+			assertEquals("\"aTable\" AS A INNER JOIN \"anotherTable\" AS B ON \"aTable\".\"name\"=\"anotherTable\".\"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()));
+			
+		}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)");
+		}
+	}
+
+}