diff --git a/src/tap/data/DataReadException.java b/src/tap/data/DataReadException.java
new file mode 100644
index 0000000000000000000000000000000000000000..f3d5db54026e94f99fa7c1bff92f57ddd1105fa5
--- /dev/null
+++ b/src/tap/data/DataReadException.java
@@ -0,0 +1,29 @@
+package tap.data;
+
+import tap.TAPException;
+
+/**
+ * Exception that occurs when reading a data input (can be an InputStream, a ResultSet, a SavotTable, ...).
+ * 
+ * @author Grégory Mantelet (ARI) - gmantele@ari.uni-heidelberg.de
+ * @version 2.0 (06/2014)
+ * @since 2.0
+ * 
+ * @see TableIterator
+ */
+public class DataReadException extends TAPException {
+	private static final long serialVersionUID = 1L;
+
+	public DataReadException(final String message){
+		super(message);
+	}
+
+	public DataReadException(Throwable cause){
+		super(cause);
+	}
+
+	public DataReadException(String message, Throwable cause){
+		super(message, cause);
+	}
+
+}
diff --git a/src/tap/data/ResultSetTableIterator.java b/src/tap/data/ResultSetTableIterator.java
new file mode 100644
index 0000000000000000000000000000000000000000..9325771b0b8485072ed052938c0554a7b2f72f94
--- /dev/null
+++ b/src/tap/data/ResultSetTableIterator.java
@@ -0,0 +1,161 @@
+package tap.data;
+
+/*
+ * This file is part of TAPLibrary.
+ * 
+ * TAPLibrary 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.
+ * 
+ * TAPLibrary 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 TAPLibrary.  If not, see <http://www.gnu.org/licenses/>.
+ * 
+ * Copyright 2014 - Astronomisches Rechen Institut (ARI)
+ */
+
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.util.NoSuchElementException;
+
+/**
+ * <p>{@link TableIterator} which lets iterate over a SQL {@link ResultSet}.</p>
+ * 
+ * <p>{@link #getColType()} will return the type declared in the {@link ResultSetMetaData} object.</p>
+ * 
+ * @author Gr&eacute;gory Mantelet (ARI) - gmantele@ari.uni-heidelberg.de
+ * @version 2.0 (06/2014)
+ * @since 2.0
+ */
+public class ResultSetTableIterator implements TableIterator {
+
+	/** ResultSet/Dataset to read. */
+	private final ResultSet data;
+
+	/** Number of columns to read. */
+	private final int nbColumns;
+	/** Type of all columns. */
+	private final String[] colTypes;
+
+	/** Indicate whether the row iteration has already started. */
+	private boolean iterationStarted = false;
+	/** Indicate whether the last row has already been reached. */
+	private boolean endReached = false;
+	/** Index of the last read column (=0 just after {@link #nextRow()} and before {@link #nextCol()}, ={@link #nbColumns} after the last column has been read). */
+	private int colIndex;
+
+	/**
+	 * Build a TableIterator able to read rows and columns of the given ResultSet.
+	 * 
+	 * @param dataSet	Dataset over which this iterator must iterate.
+	 * 
+	 * @throws NullPointerException	If NULL is given in parameter.
+	 * @throws DataReadException	If the given ResultSet is closed
+	 *                              or if the metadata (columns count and types) can not be fetched.
+	 */
+	public ResultSetTableIterator(final ResultSet dataSet) throws NullPointerException, DataReadException{
+		// A dataset MUST BE provided:
+		if (dataSet == null)
+			throw new NullPointerException("Missing ResultSet object over which to iterate!");
+
+		// It MUST also BE OPEN:
+		try{
+			if (dataSet.isClosed())
+				throw new DataReadException("Closed ResultSet: impossible to iterate over it!");
+		}catch(SQLException se){
+			throw new DataReadException("Can not know whether the ResultSet is open!", se);
+		}
+
+		// Keep a reference to the ResultSet:
+		data = dataSet;
+
+		// Count columns and determine their type:
+		try{
+			// get the metadata:
+			ResultSetMetaData metadata = data.getMetaData();
+			// count columns:
+			nbColumns = metadata.getColumnCount();
+			// determine their type:
+			colTypes = new String[nbColumns];
+			for(int i = 1; i <= nbColumns; i++)
+				colTypes[i - 1] = metadata.getColumnTypeName(i);
+		}catch(SQLException se){
+			throw new DataReadException("Can not get the column types of the given ResultSet!", se);
+		}
+	}
+
+	@Override
+	public boolean nextRow() throws DataReadException{
+		try{
+			// go to the next row:
+			boolean rowFetched = data.next();
+			endReached = !rowFetched;
+			// prepare the iteration over its columns:
+			colIndex = 0;
+			iterationStarted = true;
+			return rowFetched;
+		}catch(SQLException e){
+			throw new DataReadException("Unable to read a result set row!", e);
+		}
+	}
+
+	/**
+	 * <p>Check the row iteration state. That's to say whether:</p>
+	 * <ul>
+	 * 	<li>the row iteration has started = the first row has been read = a first call of {@link #nextRow()} has been done</li>
+	 * 	<li>AND the row iteration is not finished = the last row has been read.</li>
+	 * </ul>
+	 * @throws IllegalStateException
+	 */
+	private void checkReadState() throws IllegalStateException{
+		if (!iterationStarted)
+			throw new IllegalStateException("No row has yet been read!");
+		else if (endReached)
+			throw new IllegalStateException("End of ResultSet already reached!");
+	}
+
+	@Override
+	public boolean hasNextCol() throws IllegalStateException, DataReadException{
+		// Check the read state:
+		checkReadState();
+
+		// Determine whether the last column has been reached or not:
+		return (colIndex < nbColumns);
+	}
+
+	@Override
+	public Object nextCol() throws NoSuchElementException, IllegalStateException, DataReadException{
+		// Check the read state and ensure there is still at least one column to read:
+		if (!hasNextCol())
+			throw new NoSuchElementException("No more column to read!");
+
+		// Get the column value:
+		try{
+			return data.getObject(++colIndex);
+		}catch(SQLException se){
+			throw new DataReadException("Can not read the value of the " + colIndex + "-th column!", se);
+		}
+	}
+
+	@Override
+	public String getColType() throws IllegalStateException, DataReadException{
+		// Basically check the read state (for rows iteration):
+		checkReadState();
+
+		// Check deeper the read state (for columns iteration):
+		if (colIndex <= 0)
+			throw new IllegalStateException("No column has yet been read!");
+		else if (colIndex > nbColumns)
+			throw new IllegalStateException("All columns have already been read!");
+
+		// Return the column type:
+		return colTypes[colIndex - 1];
+	}
+
+}
diff --git a/src/tap/data/TableIterator.java b/src/tap/data/TableIterator.java
new file mode 100644
index 0000000000000000000000000000000000000000..5e6234ee389c758ae3bfdb21d552206d7c6fccd2
--- /dev/null
+++ b/src/tap/data/TableIterator.java
@@ -0,0 +1,100 @@
+package tap.data;
+
+/*
+ * This file is part of TAPLibrary.
+ * 
+ * TAPLibrary 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.
+ * 
+ * TAPLibrary 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 TAPLibrary.  If not, see <http://www.gnu.org/licenses/>.
+ * 
+ * Copyright 2014 - Astronomisches Rechen Institut (ARI)
+ */
+
+import java.util.NoSuchElementException;
+
+/**
+ * <p>Let's iterate on each row and then on each column over a table dataset.</p>
+ * 
+ * <p>Initially, no rows are loaded and the "cursor" inside the dataset is set before the first row.
+ * Thus, a first call to {@link #nextRow()} is required to read each of the column values of the first row.</p>
+ * 
+ * <p>Example of an expected usage:</p>
+ * <pre>
+ * 	TableIterator it = ...;
+ * 	try{
+ * 		while(it.nextRow()){
+ * 			while(it.hasNextCol()){
+ * 				Object colValue = it.nextCol();
+ * 				String colType = it.getColType();
+ * 				...
+ * 			}
+ * 		}
+ * 	}catch(DataReadException dre){
+ * 		...
+ * 	}
+ * </pre>
+ * 
+ * @author Gr&eacute;gory Mantelet (ARI) - gmantele@ari.uni-heidelberg.de
+ * @version 2.0 (06/2014)
+ * @since 2.0
+ */
+public interface TableIterator {
+	/**
+	 * <p>Go to the next row if there is one.</p>
+	 * 
+	 * <p><i>Note: After a call to this function the columns must be fetched individually using {@link #nextCol()}
+	 * <b>IF</b> this function returned </i>true<i>.</i></p>
+	 * 
+	 * @return	<i>true</i> if the next row has been successfully reached,
+	 *          <i>false</i> if no more rows can be read.
+	 * 
+	 * @throws DataReadException	If an error occurs while reading the table dataset.
+	 */
+	public boolean nextRow() throws DataReadException;
+
+	/**
+	 * Tell whether another column is available.
+	 * 
+	 * @return	<i>true</i> if {@link #nextCol()} will return the value of the next column with no error,
+	 *          <i>false</i> otherwise.
+	 * 
+	 * @throws IllegalStateException	If {@link #nextRow()} has not yet been called.
+	 * @throws DataReadException	If an error occurs while reading the table dataset.
+	 */
+	public boolean hasNextCol() throws IllegalStateException, DataReadException;
+
+	/**
+	 * <p>Return the value of the next column.</p>
+	 * 
+	 * <p><i>Note: The column type can be fetched using {@link #getColType()} <b>after</b> a call to {@link #nextCol()}.</i></p>
+	 * 
+	 * @return	Get the value of the next column.
+	 * 
+	 * @throws NoSuchElementException	If no more column value is available.
+	 * @throws IllegalStateException	If {@link #nextRow()} has not yet been called.
+	 * @throws DataReadException	If an error occurs while reading the table dataset.
+	 */
+	public Object nextCol() throws NoSuchElementException, IllegalStateException, DataReadException;
+
+	/**
+	 * <p>Get the type of the current column value.</p>
+	 * 
+	 * <p><i>Note: "Current column value" means here "the value last returned by {@link #nextCol()}".</i></p>
+	 * 
+	 * @return	Type of the current column value,
+	 *          or NULL if this information is not available or if this function is not implemented. 
+	 * 
+	 * @throws IllegalStateException	If {@link #nextCol()} has not yet been called.
+	 * @throws DataReadException	If an error occurs while reading the table dataset.
+	 */
+	public String getColType() throws IllegalStateException, DataReadException;
+}
diff --git a/test/tap/data/ResultSetTableIteratorTest.java b/test/tap/data/ResultSetTableIteratorTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..cd0e7f9d6da32e816791ff9d24dc0b761de1dcbc
--- /dev/null
+++ b/test/tap/data/ResultSetTableIteratorTest.java
@@ -0,0 +1,108 @@
+package tap.data;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import testtools.DBTools;
+
+public class ResultSetTableIteratorTest {
+
+	private static Connection conn;
+
+	@BeforeClass
+	public static void setUpBeforeClass() throws Exception{
+		conn = DBTools.createConnection("postgresql", "127.0.0.1", null, "gmantele", "gmantele", "pwd");
+	}
+
+	@AfterClass
+	public static void tearDownAfterClass() throws Exception{
+		DBTools.closeConnection(conn);
+	}
+
+	@Test
+	public void testWithRSNULL(){
+		try{
+			new ResultSetTableIterator(null);
+			fail("The constructor should have failed, because: the given ResultSet is NULL.");
+		}catch(Exception ex){
+			assertEquals(ex.getClass().getName(), "java.lang.NullPointerException");
+		}
+	}
+
+	@Test
+	public void testWithData(){
+		try{
+			ResultSet rs = DBTools.select(conn, "SELECT id, ra, deg, gmag FROM gums LIMIT 10;");
+
+			TableIterator it = new ResultSetTableIterator(rs);
+			final int expectedNbLines = 10, expectedNbColumns = 4;
+			int countLines = 0, countColumns = 0;
+			while(it.nextRow()){
+				// count lines:
+				countLines++;
+				// reset columns count:
+				countColumns = 0;
+				while(it.hasNextCol()){
+					it.nextCol();
+					// count columns
+					countColumns++;
+					// TEST the column type is set (not null):
+					assertTrue(it.getColType() != null);
+				}
+				// TEST that all columns have been read:
+				assertEquals(expectedNbColumns, countColumns);
+			}
+			// TEST that all lines have been read: 
+			assertEquals(expectedNbLines, countLines);
+
+		}catch(Exception ex){
+			ex.printStackTrace(System.err);
+			fail("An exception occurs while reading a correct ResultSet (containing some valid rows).");
+		}
+	}
+
+	@Test
+	public void testWithEmptySet(){
+		try{
+			ResultSet rs = DBTools.select(conn, "SELECT * FROM gums WHERE id = 'foo';");
+
+			TableIterator it = new ResultSetTableIterator(rs);
+			int countLines = 0;
+			// count lines:
+			while(it.nextRow())
+				countLines++;
+			// TEST that no line has been read: 
+			assertEquals(countLines, 0);
+
+		}catch(Exception ex){
+			ex.printStackTrace(System.err);
+			fail("An exception occurs while reading a correct ResultSet (containing some valid rows).");
+		}
+	}
+
+	@Test
+	public void testWithClosedSet(){
+		try{
+			// create a valid ResultSet:
+			ResultSet rs = DBTools.select(conn, "SELECT * FROM gums WHERE id = 'foo';");
+
+			// close the ResultSet:
+			rs.close();
+
+			// TRY to create a TableIterator with a closed ResultSet:
+			new ResultSetTableIterator(rs);
+
+			fail("The constructor should have failed, because: the given ResultSet is closed.");
+		}catch(Exception ex){
+			assertEquals(ex.getClass().getName(), "tap.data.DataReadException");
+		}
+	}
+}
diff --git a/test/testtools/DBTools.java b/test/testtools/DBTools.java
new file mode 100644
index 0000000000000000000000000000000000000000..ca032c05a6877a18d34a851be833c24013b96e76
--- /dev/null
+++ b/test/testtools/DBTools.java
@@ -0,0 +1,141 @@
+package testtools;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.HashMap;
+
+public final class DBTools {
+
+	public static int count = 0;
+
+	public final static void main(final String[] args) throws Throwable{
+		for(int i = 0; i < 3; i++){
+			Thread t = new Thread(new Runnable(){
+				@Override
+				public void run(){
+					count++;
+					try{
+						Connection conn = DBTools.createConnection("postgresql", "127.0.0.1", null, "gmantele", "gmantele", "pwd");
+						System.out.println("Start - " + count + ": ");
+						String query = "SELECT * FROM gums.smc WHERE magg BETWEEN " + (15 + count) + " AND " + (20 + count) + ";";
+						System.out.println(query);
+						ResultSet rs = DBTools.select(conn, query);
+						try{
+							rs.last();
+							System.out.println("Nb rows: " + rs.getRow());
+						}catch(SQLException e){
+							e.printStackTrace();
+						}
+						if (DBTools.closeConnection(conn))
+							System.out.println("[DEBUG] Connection closed!");
+					}catch(DBToolsException e){
+						e.printStackTrace();
+					}
+					System.out.println("End - " + count);
+					count--;
+				}
+			});
+			t.start();
+		}
+	}
+
+	public static class DBToolsException extends Exception {
+
+		private static final long serialVersionUID = 1L;
+
+		public DBToolsException(){
+			super();
+		}
+
+		public DBToolsException(String message, Throwable cause){
+			super(message, cause);
+		}
+
+		public DBToolsException(String message){
+			super(message);
+		}
+
+		public DBToolsException(Throwable cause){
+			super(cause);
+		}
+
+	}
+
+	public final static HashMap<String,String> VALUE_JDBC_DRIVERS = new HashMap<String,String>(4);
+	static{
+		VALUE_JDBC_DRIVERS.put("oracle", "oracle.jdbc.OracleDriver");
+		VALUE_JDBC_DRIVERS.put("postgresql", "org.postgresql.Driver");
+		VALUE_JDBC_DRIVERS.put("mysql", "com.mysql.jdbc.Driver");
+		VALUE_JDBC_DRIVERS.put("sqlite", "org.sqlite.JDBC");
+	}
+
+	private DBTools(){}
+
+	public final static Connection createConnection(String dbms, final String server, final String port, final String dbName, final String user, final String passwd) throws DBToolsException{
+		// 1. Resolve the DBMS and get its JDBC driver:
+		if (dbms == null)
+			throw new DBToolsException("Missing DBMS (expected: oracle, postgresql, mysql or sqlite)!");
+		dbms = dbms.toLowerCase();
+		String jdbcDriver = VALUE_JDBC_DRIVERS.get(dbms);
+		if (jdbcDriver == null)
+			throw new DBToolsException("Unknown DBMS (\"" + dbms + "\")!");
+
+		// 2. Load the JDBC driver:
+		try{
+			Class.forName(jdbcDriver);
+		}catch(ClassNotFoundException e){
+			throw new DBToolsException("Impossible to load the JDBC driver: " + e.getMessage(), e);
+		}
+
+		// TODO DEBUG MSG
+		System.out.println("[DEBUG] " + dbms + " JDBC Driver Registered!");
+
+		// 3. Establish the connection:
+		Connection connection = null;
+		try{
+			connection = DriverManager.getConnection("jdbc:" + dbms + "://" + server + ((port != null && port.trim().length() > 0) ? (":" + port) : "") + "/" + dbName, user, passwd);
+		}catch(SQLException e){
+			throw new DBToolsException("Connection failed: " + e.getMessage(), e);
+		}
+
+		if (connection == null)
+			throw new DBToolsException("Failed to make connection!");
+
+		// TODO DEBUG MSG
+		System.out.println("[DEBUG] Connection to " + dbName + " established!");
+
+		return connection;
+	}
+
+	public final static boolean closeConnection(final Connection conn) throws DBToolsException{
+		try{
+			if (conn != null && !conn.isClosed()){
+				conn.close();
+				try{
+					Thread.sleep(200);
+				}catch(InterruptedException e){
+					System.err.println("WARNING: can't wait/sleep before testing the connection close status! [" + e.getMessage() + "]");
+				}
+				return conn.isClosed();
+			}else
+				return true;
+		}catch(SQLException e){
+			throw new DBToolsException("Closing connection failed: " + e.getMessage(), e);
+		}
+	}
+
+	public final static ResultSet select(final Connection conn, final String selectQuery) throws DBToolsException{
+		if (conn == null || selectQuery == null || selectQuery.trim().length() == 0)
+			throw new DBToolsException("One parameter is missing!");
+
+		try{
+			Statement stmt = conn.createStatement();
+			return stmt.executeQuery(selectQuery);
+		}catch(SQLException e){
+			throw new DBToolsException("Can't execute the given SQL query: " + e.getMessage(), e);
+		}
+	}
+
+}