diff --git a/src/adql/parser/.gitignore b/src/adql/parser/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..5e75933c6a3ba2a311cd3cd309c6159f5c3e1845
--- /dev/null
+++ b/src/adql/parser/.gitignore
@@ -0,0 +1,3 @@
+/ADQLParser.java
+/ADQLParserConstants.java
+/ADQLParserTokenManager.java
diff --git a/src/tap/ADQLExecutor.java b/src/tap/ADQLExecutor.java
index 6b70b0cfcd6ec1c04531de85cf26b3a625b3870d..a7aee6ffbfa3456eedd0415ac960c57d4335cb30 100644
--- a/src/tap/ADQLExecutor.java
+++ b/src/tap/ADQLExecutor.java
@@ -16,7 +16,7 @@ package tap;
  * 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 2012-2013 - 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)
  */
 
@@ -26,6 +26,7 @@ import java.sql.SQLException;
 
 import javax.servlet.http.HttpServletResponse;
 
+import tap.data.TableIterator;
 import tap.db.DBConnection;
 import tap.db.DBException;
 import tap.formatter.OutputFormat;
@@ -42,20 +43,17 @@ import adql.parser.ADQLQueryFactory;
 import adql.parser.ParseException;
 import adql.parser.QueryChecker;
 import adql.query.ADQLQuery;
-import adql.translator.ADQLTranslator;
 import adql.translator.TranslationException;
 
 /**
  * 
  * 
- * @author Gr&eacute;gory Mantelet (CDS;ARI) - gmantele@ari.uni-heidelberg.de
- * @version 1.1 (12/2013)
- * 
- * @param <R>
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 2.0 (07/2014)
  */
-public class ADQLExecutor< R > {
+public class ADQLExecutor {
 
-	protected final ServiceConnection<R> service;
+	protected final ServiceConnection service;
 	protected final TAPLog logger;
 
 	protected Thread thread;
@@ -63,10 +61,10 @@ public class ADQLExecutor< R > {
 	protected HttpServletResponse response;
 	protected TAPExecutionReport report;
 
-	private DBConnection<R> dbConn = null;
+	private DBConnection dbConn = null;
 	protected TAPSchema uploadSchema = null;
 
-	public ADQLExecutor(final ServiceConnection<R> service){
+	public ADQLExecutor(final ServiceConnection service){
 		this.service = service;
 		this.logger = service.getLogger();
 	}
@@ -79,76 +77,21 @@ public class ADQLExecutor< R > {
 		return report;
 	}
 
-	public boolean hasUploadedTables(){
-		return (uploadSchema != null) && (uploadSchema.getNbTables() > 0);
-	}
-
-	protected final DBConnection<R> getDBConnection() throws TAPException{
-		return (dbConn != null) ? dbConn : (dbConn = service.getFactory().createDBConnection((report != null) ? report.jobID : null));
-	}
-
-	public final void closeDBConnection() throws TAPException{
-		if (dbConn != null){
-			dbConn.close();
-			dbConn = null;
-		}
-	}
-
-	private final void uploadTables() throws TAPException{
-		TableLoader[] tables = tapParams.getTableLoaders();
-		if (tables.length > 0){
-			logger.info("JOB " + report.jobID + "\tLoading uploaded tables (" + tables.length + ")...");
-			long start = System.currentTimeMillis();
-			try{
-				/* TODO Problem with the DBConnection! One is created here for the Uploader (and dbConn is set) and closed by its uploadTables function (but dbConn is not set to null).
-				 * Ideally, the connection should not be close, or at least dbConn should be set to null just after. */
-				uploadSchema = service.getFactory().createUploader(getDBConnection()).upload(tables);
-			}finally{
-				TAPParameters.deleteUploadedTables(tables);
-				report.setDuration(ExecutionProgression.UPLOADING, System.currentTimeMillis() - start);
-			}
-		}
-
-	}
-
-	private final R executeADQL() throws ParseException, InterruptedException, TranslationException, SQLException, TAPException, UWSException{
-		long start;
-
-		tapParams.set(TAPJob.PARAM_PROGRESSION, ExecutionProgression.PARSING);
-		start = System.currentTimeMillis();
-		ADQLQuery adql = parseADQL();
-		report.setDuration(ExecutionProgression.PARSING, System.currentTimeMillis() - start);
-
-		if (thread.isInterrupted())
-			throw new InterruptedException();
-
-		report.resultingColumns = adql.getResultingColumns();
-
-		final int limit = adql.getSelect().getLimit();
-		final Integer maxRec = tapParams.getMaxRec();
-		if (maxRec != null && maxRec > -1){
-			if (limit <= -1 || limit > maxRec)
-				adql.getSelect().setLimit(maxRec + 1);
-		}
-
-		tapParams.set(TAPJob.PARAM_PROGRESSION, ExecutionProgression.TRANSLATING);
-		start = System.currentTimeMillis();
-		String sqlQuery = translateADQL(adql);
-		report.setDuration(ExecutionProgression.TRANSLATING, System.currentTimeMillis() - start);
-		report.sqlTranslation = sqlQuery;
-
-		if (thread.isInterrupted())
-			throw new InterruptedException();
+	protected OutputFormat getFormatter() throws TAPException{
+		// Search for the corresponding formatter:
+		String format = tapParams.getFormat();
+		OutputFormat formatter = service.getOutputFormat((format == null) ? "votable" : format);
+		if (format != null && formatter == null)
+			formatter = service.getOutputFormat("votable");
 
-		tapParams.set(TAPJob.PARAM_PROGRESSION, ExecutionProgression.EXECUTING_SQL);
-		start = System.currentTimeMillis();
-		R result = executeQuery(sqlQuery, adql);
-		report.setDuration(ExecutionProgression.EXECUTING_SQL, System.currentTimeMillis() - start);
+		// Format the result:
+		if (formatter == null)
+			throw new TAPException("Impossible to format the query result: no formatter has been found for the given MIME type \"" + format + "\" and for the default MIME type \"votable\" (short form) !");
 
-		return result;
+		return formatter;
 	}
 
-	public final TAPExecutionReport start(final AsyncThread<R> thread) throws TAPException, UWSException, InterruptedException, ParseException, TranslationException, SQLException{
+	public final TAPExecutionReport start(final AsyncThread thread) throws UWSException, InterruptedException{
 		if (this.thread != null || this.report != null)
 			throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, "This ADQLExecutor has already been executed !");
 
@@ -162,7 +105,7 @@ public class ADQLExecutor< R > {
 		return start();
 	}
 
-	public final TAPExecutionReport start(final Thread thread, final String jobId, final TAPParameters params, final HttpServletResponse response) throws TAPException, UWSException, InterruptedException, ParseException, TranslationException, SQLException{
+	public final TAPExecutionReport start(final Thread thread, final String jobId, final TAPParameters params, final HttpServletResponse response) throws TAPException, InterruptedException{
 		if (this.thread != null || this.report != null)
 			throw new TAPException("This ADQLExecutor has already been executed !");
 
@@ -177,7 +120,10 @@ public class ADQLExecutor< R > {
 	protected final TAPExecutionReport start() throws TAPException, UWSException, InterruptedException, ParseException, TranslationException, SQLException{
 		long start = System.currentTimeMillis();
 		try{
-			// Upload tables if needed:
+			// Get a "database" connection:
+			dbConn = service.getFactory().createDBConnection(report.jobID);
+
+			// 1. UPLOAD TABLES, if needed:
 			if (tapParams != null && tapParams.getTableLoaders() != null && tapParams.getTableLoaders().length > 0){
 				tapParams.set(TAPJob.PARAM_PROGRESSION, ExecutionProgression.UPLOADING);
 				uploadTables();
@@ -186,18 +132,24 @@ public class ADQLExecutor< R > {
 			if (thread.isInterrupted())
 				throw new InterruptedException();
 
-			// Parse, translate in SQL and execute the ADQL query:
-			R queryResult = executeADQL();
-			if (queryResult == null || thread.isInterrupted())
+			// 2. PARSE THE ADQL QUERY:
+			tapParams.set(TAPJob.PARAM_PROGRESSION, ExecutionProgression.PARSING);
+			ADQLQuery adqlQuery = parseADQL();
+
+			if (adqlQuery == null || thread.isInterrupted())
 				throw new InterruptedException();
 
-			// Write the result:
+			// 3. EXECUTE THE ADQL QUERY:
+			tapParams.set(TAPJob.PARAM_PROGRESSION, ExecutionProgression.EXECUTING_ADQL);
+			TableIterator queryResult = executeADQL(adqlQuery);
+
+			// 4. WRITE RESULT:
 			tapParams.set(TAPJob.PARAM_PROGRESSION, ExecutionProgression.WRITING_RESULT);
 			writeResult(queryResult);
 
+			// Report the COMPLETED status:
 			logger.info("JOB " + report.jobID + " COMPLETED");
 			tapParams.set(TAPJob.PARAM_PROGRESSION, ExecutionProgression.FINISHED);
-
 			report.success = true;
 
 			return report;
@@ -211,7 +163,10 @@ public class ADQLExecutor< R > {
 				logger.error("JOB " + report.jobID + "\tCan not drop uploaded tables !", e);
 			}
 			try{
-				closeDBConnection();
+				if (dbConn != null){
+					dbConn.close();
+					dbConn = null;
+				}
 			}catch(TAPException e){
 				logger.error("JOB " + report.jobID + "\tCan not close the DB connection !", e);
 			}
@@ -220,7 +175,27 @@ public class ADQLExecutor< R > {
 		}
 	}
 
+	private final void uploadTables() throws TAPException{
+		// Fetch the tables to upload:
+		TableLoader[] tables = tapParams.getTableLoaders();
+
+		// Upload them, if needed:
+		if (tables.length > 0){
+			logger.info("JOB " + report.jobID + "\tLoading uploaded tables (" + tables.length + ")...");
+			long start = System.currentTimeMillis();
+			try{
+				/* TODO Problem with the DBConnection! One is created here for the Uploader (and dbConn is set) and closed by its uploadTables function (but dbConn is not set to null).
+				 * Ideally, the connection should not be close, or at least dbConn should be set to null just after. */
+				uploadSchema = service.getFactory().createUploader(dbConn).upload(tables);
+			}finally{
+				TAPParameters.deleteUploadedTables(tables);
+				report.setDuration(ExecutionProgression.UPLOADING, System.currentTimeMillis() - start);
+			}
+		}
+	}
+
 	protected ADQLQuery parseADQL() throws ParseException, InterruptedException, TAPException{
+		long start = System.currentTimeMillis();
 		ADQLQueryFactory queryFactory = service.getFactory().createQueryFactory();
 		QueryChecker queryChecker = service.getFactory().createQueryChecker(uploadSchema);
 		ADQLParser parser;
@@ -230,22 +205,21 @@ public class ADQLExecutor< R > {
 			parser = new ADQLParser(queryChecker, queryFactory);
 		parser.setCoordinateSystems(service.getCoordinateSystems());
 		parser.setDebug(false);
-		//logger.info("Job "+report.jobID+" - 1/5 Parsing ADQL....");
-		return parser.parseQuery(tapParams.getQuery());
-	}
-
-	protected String translateADQL(ADQLQuery query) throws TranslationException, InterruptedException, TAPException{
-		ADQLTranslator translator = service.getFactory().createADQLTranslator();
-		//logger.info("Job "+report.jobID+" - 2/5 Translating ADQL...");
-		return translator.translate(query);
+		ADQLQuery query = parser.parseQuery(tapParams.getQuery());
+		final int limit = query.getSelect().getLimit();
+		final Integer maxRec = tapParams.getMaxRec();
+		if (maxRec != null && maxRec > -1){
+			if (limit <= -1 || limit > maxRec)
+				query.getSelect().setLimit(maxRec + 1);
+		}
+		report.setDuration(ExecutionProgression.PARSING, System.currentTimeMillis() - start);
+		report.resultingColumns = query.getResultingColumns();
+		return query;
 	}
 
-	protected R executeQuery(String sql, ADQLQuery adql) throws SQLException, InterruptedException, TAPException{
-		//logger.info("Job "+report.jobID+" - 3/5 Creating DBConnection....");
-		DBConnection<R> dbConn = getDBConnection();
-		//logger.info("Job "+report.jobID+" - 4/5 Executing query...\n"+sql);
+	protected TableIterator executeADQL(ADQLQuery adql) throws SQLException, InterruptedException, TAPException{
 		final long startTime = System.currentTimeMillis();
-		R result = dbConn.executeQuery(sql, adql);
+		TableIterator result = dbConn.executeQuery(adql);
 		if (result == null)
 			logger.info("JOB " + report.jobID + " - QUERY ABORTED AFTER " + (System.currentTimeMillis() - startTime) + " MS !");
 		else
@@ -253,22 +227,8 @@ public class ADQLExecutor< R > {
 		return result;
 	}
 
-	protected OutputFormat<R> getFormatter() throws TAPException{
-		// Search for the corresponding formatter:
-		String format = tapParams.getFormat();
-		OutputFormat<R> formatter = service.getOutputFormat((format == null) ? "votable" : format);
-		if (format != null && formatter == null)
-			formatter = service.getOutputFormat("votable");
-
-		// Format the result:
-		if (formatter == null)
-			throw new TAPException("Impossible to format the query result: no formatter has been found for the given MIME type \"" + format + "\" and for the default MIME type \"votable\" (short form) !");
-
-		return formatter;
-	}
-
-	protected final void writeResult(R queryResult) throws InterruptedException, TAPException, UWSException{
-		OutputFormat<R> formatter = getFormatter();
+	protected final void writeResult(TableIterator queryResult) throws InterruptedException, TAPException, UWSException{
+		OutputFormat formatter = getFormatter();
 
 		// Synchronous case:
 		if (response != null){
@@ -300,7 +260,7 @@ public class ADQLExecutor< R > {
 		}
 	}
 
-	protected void writeResult(R queryResult, OutputFormat<R> formatter, OutputStream output) throws InterruptedException, TAPException{
+	protected void writeResult(TableIterator queryResult, OutputFormat formatter, OutputStream output) throws InterruptedException, TAPException{
 		//logger.info("Job "+report.jobID+" - 5/5 Writing result file...");
 		formatter.writeResult(queryResult, output, report, thread);
 	}
@@ -308,15 +268,13 @@ public class ADQLExecutor< R > {
 	protected void dropUploadedTables() throws TAPException{
 		if (uploadSchema != null){
 			// Drop all uploaded tables:
-			DBConnection<R> dbConn = getDBConnection();
 			for(TAPTable t : uploadSchema){
 				try{
-					dbConn.dropTable(t);
+					dbConn.dropUploadedTable(t.getDBName());
 				}catch(DBException dbe){
 					logger.error("JOB " + report.jobID + "\tCan not drop the table \"" + t.getDBName() + "\" (in adql \"" + t.getADQLName() + "\") from the database !", dbe);
 				}
 			}
-			closeDBConnection();
 		}
 	}
 
diff --git a/src/tap/AbstractTAPFactory.java b/src/tap/AbstractTAPFactory.java
index 91bad42e857dae762b36d571b0a589b5bdf8c75d..73de9b13352db805a443a2eaed37231fee103955 100644
--- a/src/tap/AbstractTAPFactory.java
+++ b/src/tap/AbstractTAPFactory.java
@@ -32,33 +32,27 @@ import tap.metadata.TAPMetadata;
 import tap.metadata.TAPSchema;
 import tap.metadata.TAPTable;
 import tap.parameters.TAPParameters;
-
 import tap.upload.Uploader;
-
 import uws.UWSException;
-
 import uws.job.ErrorSummary;
 import uws.job.JobThread;
 import uws.job.Result;
 import uws.job.UWSJob;
-
 import uws.job.parameters.UWSParameters;
 import uws.job.user.JobOwner;
-
 import uws.service.AbstractUWSFactory;
 import uws.service.UWSService;
 import uws.service.backup.UWSBackupManager;
 import adql.db.DBChecker;
 import adql.db.DBTable;
-
 import adql.parser.ADQLQueryFactory;
 import adql.parser.QueryChecker;
 
-public abstract class AbstractTAPFactory< R > extends AbstractUWSFactory implements TAPFactory<R> {
+public abstract class AbstractTAPFactory extends AbstractUWSFactory implements TAPFactory {
 
-	protected final ServiceConnection<R> service;
+	protected final ServiceConnection service;
 
-	protected AbstractTAPFactory(ServiceConnection<R> service) throws NullPointerException{
+	protected AbstractTAPFactory(ServiceConnection service) throws NullPointerException{
 		if (service == null)
 			throw new NullPointerException("Can not create a TAPFactory without a ServiceConnection instance !");
 
@@ -102,14 +96,15 @@ public abstract class AbstractTAPFactory< R > extends AbstractUWSFactory impleme
 	@Override
 	public final JobThread createJobThread(final UWSJob job) throws UWSException{
 		try{
-			return new AsyncThread<R>((TAPJob)job, createADQLExecutor());
+			return new AsyncThread((TAPJob)job, createADQLExecutor());
 		}catch(TAPException te){
 			throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, te, "Impossible to create an AsyncThread !");
 		}
 	}
 
-	public ADQLExecutor<R> createADQLExecutor() throws TAPException{
-		return new ADQLExecutor<R>(service);
+	@Override
+	public ADQLExecutor createADQLExecutor() throws TAPException{
+		return new ADQLExecutor(service);
 	}
 
 	/**
@@ -155,7 +150,8 @@ public abstract class AbstractTAPFactory< R > extends AbstractUWSFactory impleme
 		return new DBChecker(tables);
 	}
 
-	public Uploader createUploader(final DBConnection<R> dbConn) throws TAPException{
+	@Override
+	public Uploader createUploader(final DBConnection dbConn) throws TAPException{
 		return new Uploader(service, dbConn);
 	}
 
diff --git a/src/tap/AsyncThread.java b/src/tap/AsyncThread.java
index 4581c0900f4cb93f8b9814e9ead36d9fc690a33d..7d0d0489da1e6cd636ab61df91a37cf1d4e6d094 100644
--- a/src/tap/AsyncThread.java
+++ b/src/tap/AsyncThread.java
@@ -19,17 +19,16 @@ package tap;
  * Copyright 2012 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
  */
 
-import adql.parser.ParseException;
-import adql.translator.TranslationException;
 import uws.UWSException;
-
 import uws.job.JobThread;
+import adql.parser.ParseException;
+import adql.translator.TranslationException;
 
-public class AsyncThread< R > extends JobThread {
+public class AsyncThread extends JobThread {
 
-	protected final ADQLExecutor<R> executor;
+	protected final ADQLExecutor executor;
 
-	public AsyncThread(TAPJob j, ADQLExecutor<R> executor) throws UWSException{
+	public AsyncThread(TAPJob j, ADQLExecutor executor) throws UWSException{
 		super(j, "Execute the ADQL query of the TAP request " + j.getJobId());
 		this.executor = executor;
 	}
diff --git a/src/tap/ExecutionProgression.java b/src/tap/ExecutionProgression.java
index 618d2104813c7d0584be6f136e4b4390b3ef95a6..4086ccdec638ef9b3139e7dae75949c38e291884 100644
--- a/src/tap/ExecutionProgression.java
+++ b/src/tap/ExecutionProgression.java
@@ -16,9 +16,16 @@ package tap;
  * 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 2012 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomisches Rechen Institut (ARI)
  */
 
+/**
+ * Let describe the current status of a job execution.
+ * 
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 2.0 (07/2014)
+ */
 public enum ExecutionProgression{
-	PENDING, UPLOADING, PARSING, TRANSLATING, EXECUTING_SQL, WRITING_RESULT, FINISHED;
+	PENDING, UPLOADING, PARSING, EXECUTING_ADQL, WRITING_RESULT, FINISHED;
 }
diff --git a/src/tap/ServiceConnection.java b/src/tap/ServiceConnection.java
index 40e3745205e86a5a6dcea1cdd9fa647eb5459935..6a2782b7f4ba6190f677d9afe681347ee48021b0 100644
--- a/src/tap/ServiceConnection.java
+++ b/src/tap/ServiceConnection.java
@@ -23,16 +23,12 @@ import java.util.Collection;
 import java.util.Iterator;
 
 import tap.file.TAPFileManager;
-
 import tap.formatter.OutputFormat;
-
 import tap.log.TAPLog;
-
 import tap.metadata.TAPMetadata;
-
 import uws.service.UserIdentifier;
 
-public interface ServiceConnection< R > {
+public interface ServiceConnection {
 
 	public static enum LimitUnit{
 		rows, bytes;
@@ -70,12 +66,12 @@ public interface ServiceConnection< R > {
 
 	public TAPLog getLogger();
 
-	public TAPFactory<R> getFactory();
+	public TAPFactory getFactory();
 
 	public TAPFileManager getFileManager();
 
-	public Iterator<OutputFormat<R>> getOutputFormats();
+	public Iterator<OutputFormat> getOutputFormats();
 
-	public OutputFormat<R> getOutputFormat(final String mimeOrAlias);
+	public OutputFormat getOutputFormat(final String mimeOrAlias);
 
 }
diff --git a/src/tap/TAPExecutionReport.java b/src/tap/TAPExecutionReport.java
index bf95af1a449dd836cc4b16f1c65960e90d7db4b4..45ce99b893f9d2e214bc0404b64b789c55d15a7f 100644
--- a/src/tap/TAPExecutionReport.java
+++ b/src/tap/TAPExecutionReport.java
@@ -16,90 +16,163 @@ package tap;
  * 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 2012 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomisches Rechen Institut (ARI)
  */
 
-import adql.db.DBColumn;
-
 import tap.parameters.TAPParameters;
+import adql.db.DBColumn;
 
+/**
+ * <p>Report the execution (including the parsing and the output writing) of an ADQL query.
+ * It gives information on the job parameters, the job ID, whether it is a synchronous task or not, times of each execution step (uploading, parsing, executing and writing),
+ * the resulting columns and the success or not of the execution.</p>
+ * 
+ * <p>This report is completely filled by {@link ADQLExecutor}, and aims to be used/read only at the end of the job or when it is definitely finished.</p>
+ * 
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 2.0 (07/2014)
+ */
 public class TAPExecutionReport {
 
+	/** ID of the job whose the execution is reported here. */
 	public final String jobID;
+
+	/** Indicate whether this execution is done in a synchronous or asynchronous job. */
 	public final boolean synchronous;
+
+	/** List of all parameters provided in the user request. */
 	public final TAPParameters parameters;
 
-	public String sqlTranslation = null;
+	/** List of all resulting columns. <i>Empty array, if not yet known.</i> */
 	public DBColumn[] resultingColumns = new DBColumn[0];
 
-	protected final long[] durations = new long[]{-1,-1,-1,-1,-1};
+	/** Duration of all execution steps. <i>For the moment only 4 steps (in the order): uploading, parsing, executing and writing.</i> */
+	protected final long[] durations = new long[]{-1,-1,-1,-1};
+
+	/** Total duration of the job execution. */
 	protected long totalDuration = -1;
 
+	/** Indicate whether this job has ended successfully or not. <i>At the beginning or while executing, this field is always FALSE.</i> */
 	public boolean success = false;
 
+	/**
+	 * Build an empty execution report.
+	 * 
+	 * @param jobID			ID of the job whose the execution must be described here.
+	 * @param synchronous	<i>true</i> if the job is synchronous, <i>false</i> otherwise.
+	 * @param params		List of all parameters provided by the user for the execution.
+	 */
 	public TAPExecutionReport(final String jobID, final boolean synchronous, final TAPParameters params){
 		this.jobID = jobID;
 		this.synchronous = synchronous;
 		parameters = params;
 	}
 
+	/**
+	 * <p>Map the execution progression with an index inside the {@link #durations} array.</p>
+	 * 
+	 * <p><i><b>Warning:</b> for the moment, only {@link ExecutionProgression#UPLOADING}, {@link ExecutionProgression#PARSING},
+	 * {@link ExecutionProgression#EXECUTING_ADQL} and {@link ExecutionProgression#WRITING_RESULT} are managed.</i></p>
+	 * 
+	 * @param tapProgression	Execution progression.
+	 * 
+	 * @return	Index in the array {@link #durations}, or -1 if the given execution progression is not managed.
+	 */
 	protected int getIndexDuration(final ExecutionProgression tapProgression){
 		switch(tapProgression){
 			case UPLOADING:
 				return 0;
 			case PARSING:
 				return 1;
-			case TRANSLATING:
+			case EXECUTING_ADQL:
 				return 2;
-			case EXECUTING_SQL:
-				return 3;
 			case WRITING_RESULT:
-				return 4;
+				return 3;
 			default:
 				return -1;
 		}
 	}
 
-	public final long getDuration(final ExecutionProgression tapProgression){
-		int indDuration = getIndexDuration(tapProgression);
+	/**
+	 * Get the duration corresponding to the given job execution step.
+	 * 
+	 * @param tapStep	Job execution step.
+	 * 
+	 * @return	The corresponding duration (in ms), or -1 if this step has not been (yet) processed.
+	 * 
+	 * @see #getIndexDuration(ExecutionProgression)
+	 */
+	public final long getDuration(final ExecutionProgression tapStep){
+		int indDuration = getIndexDuration(tapStep);
 		if (indDuration < 0 || indDuration >= durations.length)
 			return -1;
 		else
 			return durations[indDuration];
 	}
 
-	public final void setDuration(final ExecutionProgression tapProgression, final long duration){
-		int indDuration = getIndexDuration(tapProgression);
+	/**
+	 * Set the duration corresponding to the given execution step.
+	 * 
+	 * @param tapStep	Job execution step.
+	 * @param duration	Duration (in ms) of the given execution step.
+	 */
+	public final void setDuration(final ExecutionProgression tapStep, final long duration){
+		int indDuration = getIndexDuration(tapStep);
 		if (indDuration < 0 || indDuration >= durations.length)
 			return;
 		else
 			durations[indDuration] = duration;
 	}
 
+	/**
+	 * Get the execution of the UPLOAD step.
+	 * @return Duration (in ms).
+	 * @see #getDuration(ExecutionProgression)
+	 */
 	public final long getUploadDuration(){
 		return getDuration(ExecutionProgression.UPLOADING);
 	}
 
+	/**
+	 * Get the execution of the PARSE step.
+	 * @return Duration (in ms).
+	 * @see #getDuration(ExecutionProgression)
+	 */
 	public final long getParsingDuration(){
 		return getDuration(ExecutionProgression.PARSING);
 	}
 
-	public final long getTranslationDuration(){
-		return getDuration(ExecutionProgression.TRANSLATING);
-	}
-
+	/**
+	 * Get the execution of the EXECUTION step.
+	 * @return Duration (in ms).
+	 * @see #getDuration(ExecutionProgression)
+	 */
 	public final long getExecutionDuration(){
-		return getDuration(ExecutionProgression.EXECUTING_SQL);
+		return getDuration(ExecutionProgression.EXECUTING_ADQL);
 	}
 
+	/**
+	 * Get the execution of the FORMAT step.
+	 * @return Duration (in ms).
+	 * @see #getDuration(ExecutionProgression)
+	 */
 	public final long getFormattingDuration(){
 		return getDuration(ExecutionProgression.WRITING_RESULT);
 	}
 
+	/**
+	 * Get the total duration of the job execution.
+	 * @return	Duration (in ms).
+	 */
 	public final long getTotalDuration(){
 		return totalDuration;
 	}
 
+	/**
+	 * Set the total duration of the job execution.
+	 * @param duration	Duration (in ms) to set.
+	 */
 	public final void setTotalDuration(final long duration){
 		totalDuration = duration;
 	}
diff --git a/src/tap/TAPFactory.java b/src/tap/TAPFactory.java
index 785be4795659e53d11bc2b0ffecf32477784f5dd..9911edd183039112cf0f2d6eaf93102311a36f55 100644
--- a/src/tap/TAPFactory.java
+++ b/src/tap/TAPFactory.java
@@ -20,30 +20,23 @@ package tap;
  */
 
 import tap.db.DBConnection;
-
 import tap.metadata.TAPSchema;
-
 import tap.upload.Uploader;
-
 import uws.UWSException;
-
 import uws.service.UWSFactory;
 import uws.service.UWSService;
-
 import uws.service.backup.UWSBackupManager;
-
 import adql.parser.ADQLQueryFactory;
 import adql.parser.QueryChecker;
-
 import adql.translator.ADQLTranslator;
 
-public interface TAPFactory< R > extends UWSFactory {
+public interface TAPFactory extends UWSFactory {
 
 	public UWSService createUWS() throws TAPException, UWSException;
 
 	public UWSBackupManager createUWSBackupManager(final UWSService uws) throws TAPException, UWSException;
 
-	public ADQLExecutor<R> createADQLExecutor() throws TAPException;
+	public ADQLExecutor createADQLExecutor() throws TAPException;
 
 	public ADQLQueryFactory createQueryFactory() throws TAPException;
 
@@ -51,8 +44,8 @@ public interface TAPFactory< R > extends UWSFactory {
 
 	public ADQLTranslator createADQLTranslator() throws TAPException;
 
-	public DBConnection<R> createDBConnection(final String jobID) throws TAPException;
+	public DBConnection createDBConnection(final String jobID) throws TAPException;
 
-	public Uploader createUploader(final DBConnection<R> dbConn) throws TAPException;
+	public Uploader createUploader(final DBConnection dbConn) throws TAPException;
 
 }
diff --git a/src/tap/TAPSyncJob.java b/src/tap/TAPSyncJob.java
index 0f26d275d67e8512c1d7c73fffcb8d15741a8abe..2a87027c858427046a3ec66a230f4f1da4f265f8 100644
--- a/src/tap/TAPSyncJob.java
+++ b/src/tap/TAPSyncJob.java
@@ -34,7 +34,7 @@ public class TAPSyncJob {
 
 	protected static String lastId = null;
 
-	protected final ServiceConnection<?> service;
+	protected final ServiceConnection service;
 
 	protected final String ID;
 	protected final TAPParameters tapParams;
@@ -45,7 +45,7 @@ public class TAPSyncJob {
 
 	private Date startedAt = null;
 
-	public TAPSyncJob(final ServiceConnection<?> service, final TAPParameters params) throws NullPointerException{
+	public TAPSyncJob(final ServiceConnection service, final TAPParameters params) throws NullPointerException{
 		if (params == null)
 			throw new NullPointerException("Missing TAP parameters ! => Impossible to create a synchronous TAP job.");
 		tapParams = params;
@@ -95,7 +95,7 @@ public class TAPSyncJob {
 		if (startedAt != null)
 			throw new IllegalStateException("Impossible to restart a synchronous TAP query !");
 
-		ADQLExecutor<?> executor;
+		ADQLExecutor executor;
 		try{
 			executor = service.getFactory().createADQLExecutor();
 		}catch(TAPException e){
@@ -141,14 +141,14 @@ public class TAPSyncJob {
 	public class SyncThread extends Thread {
 
 		private final String taskDescription;
-		public final ADQLExecutor<?> executor;
+		public final ADQLExecutor executor;
 		protected final HttpServletResponse response;
 		protected final String ID;
 		protected final TAPParameters tapParams;
 		protected Throwable exception = null;
 		protected TAPExecutionReport report = null;
 
-		public SyncThread(final ADQLExecutor<?> executor, final String ID, final TAPParameters tapParams, final HttpServletResponse response){
+		public SyncThread(final ADQLExecutor executor, final String ID, final TAPParameters tapParams, final HttpServletResponse response){
 			super(JobThread.tg, ID);
 			taskDescription = "Executing the synchronous TAP query " + ID;
 			this.executor = executor;
diff --git a/src/tap/backup/DefaultTAPBackupManager.java b/src/tap/backup/DefaultTAPBackupManager.java
index 4e4a86f48d84062a03a162b2a9fd8e1bc00c1366..f84e066a0066bd0398526fb6a536daa757033c95 100644
--- a/src/tap/backup/DefaultTAPBackupManager.java
+++ b/src/tap/backup/DefaultTAPBackupManager.java
@@ -16,7 +16,8 @@ package tap.backup;
  * 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 2012 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomisches Rechen Institut (ARI)
  */
 
 import org.json.JSONException;
@@ -30,20 +31,64 @@ import uws.job.UWSJob;
 import uws.service.UWS;
 import uws.service.backup.DefaultUWSBackupManager;
 
+/**
+ * <p>Let backup all TAP asynchronous jobs.</p>
+ * 
+ * <p><i>note: Basically the saved data are the same, but in addition some execution statistics are also added.</i></p>
+ * 
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 2.0 (07/2014)
+ * 
+ * @see DefaultUWSBackupManager
+ */
 public class DefaultTAPBackupManager extends DefaultUWSBackupManager {
 
+	/**
+	 * Build a default TAP jobs backup manager.
+	 * 
+	 * @param uws	The UWS containing all the jobs to backup.
+	 * 
+	 * @see DefaultUWSBackupManager#DefaultUWSBackupManager(UWS)
+	 */
 	public DefaultTAPBackupManager(UWS uws){
 		super(uws);
 	}
 
+	/**
+	 * Build a default TAP jobs backup manager.
+	 * 
+	 * @param uws		The UWS containing all the jobs to backup.
+	 * @param frequency	The backup frequency (in ms ; MUST BE positive and different from 0.
+	 *                  If negative or 0, the frequency will be automatically set to DEFAULT_FREQUENCY).
+	 * 
+	 * @see DefaultUWSBackupManager#DefaultUWSBackupManager(UWS, long)
+	 */
 	public DefaultTAPBackupManager(UWS uws, long frequency){
 		super(uws, frequency);
 	}
 
+	/**
+	 * Build a default TAP jobs backup manager.
+	 * 
+	 * @param uws		The UWS containing all the jobs to backup.
+	 * @param byUser	Backup mode.
+	 * 
+	 * @see DefaultUWSBackupManager#DefaultUWSBackupManager(UWS, boolean)
+	 */
 	public DefaultTAPBackupManager(UWS uws, boolean byUser) throws UWSException{
 		super(uws, byUser);
 	}
 
+	/**
+	 * Build a default TAP jobs backup manager.
+	 * 
+	 * @param uws		The UWS containing all the jobs to backup.
+	 * @param byUser	Backup mode.
+	 * @param frequency	The backup frequency (in ms ; MUST BE positive and different from 0.
+	 *                  If negative or 0, the frequency will be automatically set to DEFAULT_FREQUENCY).
+	 * 
+	 * @see DefaultUWSBackupManager#DefaultUWSBackupManager(UWS, boolean, long)
+	 */
 	public DefaultTAPBackupManager(UWS uws, boolean byUser, long frequency) throws UWSException{
 		super(uws, byUser, frequency);
 	}
@@ -57,10 +102,8 @@ public class DefaultTAPBackupManager extends DefaultUWSBackupManager {
 
 			JSONObject jsonExecReport = new JSONObject();
 			jsonExecReport.put("success", execReport.success);
-			jsonExecReport.put("sql", execReport.sqlTranslation);
 			jsonExecReport.put("uploadduration", execReport.getUploadDuration());
 			jsonExecReport.put("parsingduration", execReport.getParsingDuration());
-			jsonExecReport.put("translationduration", execReport.getTranslationDuration());
 			jsonExecReport.put("executionduration", execReport.getExecutionDuration());
 			jsonExecReport.put("formattingduration", execReport.getFormattingDuration());
 			jsonExecReport.put("totalduration", execReport.getTotalDuration());
@@ -90,16 +133,12 @@ public class DefaultTAPBackupManager extends DefaultUWSBackupManager {
 						try{
 							if (key.equalsIgnoreCase("success"))
 								execReport.success = jsonExecReport.getBoolean(key);
-							else if (key.equalsIgnoreCase("sql"))
-								execReport.sqlTranslation = jsonExecReport.getString(key);
 							else if (key.equalsIgnoreCase("uploadduration"))
 								execReport.setDuration(ExecutionProgression.UPLOADING, jsonExecReport.getLong(key));
 							else if (key.equalsIgnoreCase("parsingduration"))
 								execReport.setDuration(ExecutionProgression.PARSING, jsonExecReport.getLong(key));
-							else if (key.equalsIgnoreCase("translationduration"))
-								execReport.setDuration(ExecutionProgression.TRANSLATING, jsonExecReport.getLong(key));
 							else if (key.equalsIgnoreCase("executionduration"))
-								execReport.setDuration(ExecutionProgression.EXECUTING_SQL, jsonExecReport.getLong(key));
+								execReport.setDuration(ExecutionProgression.EXECUTING_ADQL, jsonExecReport.getLong(key));
 							else if (key.equalsIgnoreCase("formattingduration"))
 								execReport.setDuration(ExecutionProgression.WRITING_RESULT, jsonExecReport.getLong(key));
 							else if (key.equalsIgnoreCase("totalduration"))
diff --git a/src/tap/db/DBConnection.java b/src/tap/db/DBConnection.java
index 695cf14ffde4c236e56b6c99157cb9b5a2aefee2..00a5dbac2e1537d955ff1a51eee3586f1896b122 100644
--- a/src/tap/db/DBConnection.java
+++ b/src/tap/db/DBConnection.java
@@ -16,47 +16,114 @@ package tap.db;
  * 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 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 cds.savot.model.SavotTR;
+import tap.data.DataReadException;
+import tap.data.TableIterator;
+import tap.metadata.TAPDM;
 import tap.metadata.TAPTable;
 import uws.service.log.UWSLogType;
-
 import adql.query.ADQLQuery;
 
 /**
- * TODO
+ * <p>Connection to the "database" (whatever is the type or whether it is linked to a true DBMS connection).</p>
  * 
- * @author Gr&eacute;gory Mantelet (CDS)
- * @version 06/2012
+ * <p>It lets executing ADQL queries and updating the TAP datamodel (with the list of schemas, tables and columns published in TAP,
+ * or with uploaded tables).</p>
  * 
- * @param <R>	Result type of the execution of a query (see {@link #executeQuery(String, ADQLQuery)}.
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 2.0 (07/2014)
  */
-public interface DBConnection< R > {
+public interface DBConnection {
 
+	/** Log type specific to the database activity.
+	 * @see UWSLogType#createCustomLogType(String) */
 	public final static UWSLogType LOG_TYPE_DB_ACTIVITY = UWSLogType.createCustomLogType("DBActivity");
 
+	/**
+	 * <p>Get any identifier for this connection.</p>
+	 * 
+	 * <p><i>note: it is used only for logging purpose.</i></p>
+	 * 
+	 * @return	ID of this connection.
+	 */
 	public String getID();
 
-	public void startTransaction() throws DBException;
-
-	public void cancelTransaction() throws DBException;
-
-	public void endTransaction() throws DBException;
-
-	public R executeQuery(final String sqlQuery, final ADQLQuery adqlQuery) throws DBException;
-
-	public void createSchema(final String schemaName) throws DBException;
-
-	public void dropSchema(final String schemaName) throws DBException;
-
-	public void createTable(final TAPTable table) throws DBException;
-
-	public void insertRow(final SavotTR row, final TAPTable table) throws DBException;
-
-	public void dropTable(final TAPTable table) throws DBException;
-
+	/**
+	 * <p>Let executing the given ADQL query.</p>
+	 * 
+	 * <p>The result of this query must be formatted as a table, and so must be iterable using a {@link TableIterator}.</p>
+	 * 
+	 * <p><i>note: the interpretation of the ADQL query is up to the implementation. In most of the case, it is just needed
+	 * to translate this ADQL query into an SQL query (understandable by the chosen DBMS).</i></p>
+	 * 
+	 * @param adqlQuery	ADQL query to execute.
+	 * 
+	 * @return	The table result.
+	 * 
+	 * @throws DBException	If any errors occurs while executing the query.
+	 */
+	public TableIterator executeQuery(final ADQLQuery adqlQuery) throws DBException;
+
+	/**
+	 * <p>Add or update the specified TAP_SCHEMA table with the given data.</p>
+	 * 
+	 * <p><i><b>Warning:</b> It is expected that the given data SHOULD be the only ones inside the specified table.
+	 * So, the table SHOULD probably be cleared before the insertion of the given data. However, this behavior MAY depend of the
+	 * implementation and more particularly of the way the TAP_SCHEMA is updated.</i></p>
+	 * 
+	 * @param tapTableName	Name of the TAP_SCHEMA table to add/update.
+	 * @param data			Data to use in order to fill the specified table.
+	 * 
+	 * @return				<i>true</i> if the specified table has been successfully added/updated, <i>false</i> otherwise.
+	 * 
+	 * @throws DBException			If any error occurs while updating the database.
+	 * @throws DataReadException	If any error occurs while reading the given data.
+	 */
+	public boolean updateTAPTable(final TAPDM tapTableName, final TableIterator data) throws DBException, DataReadException;
+
+	/**
+	 * Add the defined and given table inside the TAP_UPLOAD schema.
+	 * 
+	 * <p><i>note: A table of TAP_UPLOAD MUST be transient and persistent only for the lifetime of the query.
+	 * So, this function should always be used with {@link #dropUploadedTable(String)}, which is called at
+	 * the end of each query execution.</i></p> 
+	 * 
+	 * @param tableDef	Definition of the table to upload (list of all columns and of their type).
+	 * @param data		Rows and columns of the table to upload.
+	 * @param maxNbRows	Maximum number of rows allowed to be inserted. Beyond this limit, a
+	 *                  {@link DataReadException} MUST be sent. <i>A negative or a NULL value means "no limit".</i>
+	 * 
+	 * @return			<i>true</i> if the given table has been successfully added, <i>false</i> otherwise.
+	 * 
+	 * @throws DBException			If any error occurs while adding the table.
+	 * @throws DataReadException	If any error occurs while reading the given data.
+	 */
+	public boolean addUploadedTable(final TAPTable tableDef, final TableIterator data, final int maxNbRows) throws DBException, DataReadException;
+
+	/**
+	 * <p>Drop the specified uploaded table from the database.
+	 * More precisely, it means dropping a table from the TAP_UPLOAD schema.</p>
+	 * 
+	 * @param tableName	Name (in the database) of the uploaded table to drop.
+	 * 
+	 * @return	<i>true</i> if the specified table has been successfully dropped, <i>false</i> otherwise.
+	 * 
+	 * @throws DBException	If any error occurs while dropping the specified uploaded table.
+	 */
+	public boolean dropUploadedTable(final String tableName) throws DBException;
+
+	/**
+	 * <p>Close the connection (if needed).</p>
+	 * 
+	 * <p><i>note: This function is called at the end of a query/job execution, after the result
+	 * has been successfully (or not) fetched. When called, it means the connection is no longer needed
+	 * for the job and so, can be freed (or given back to a pool, for instance).</i></p>
+	 * 
+	 * @throws DBException	If any error occurs while closing the connection.
+	 */
 	public void close() throws DBException;
 
 }
diff --git a/src/tap/error/DefaultTAPErrorWriter.java b/src/tap/error/DefaultTAPErrorWriter.java
index 40534f08d768a3a4a23a3ac052808d79c0e3dde3..85236b914e9ded059ab07137abad0249916f8900 100644
--- a/src/tap/error/DefaultTAPErrorWriter.java
+++ b/src/tap/error/DefaultTAPErrorWriter.java
@@ -16,7 +16,8 @@ package tap.error;
  * 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 2012 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomisches Rechen Institut (ARI)
  */
 
 import java.io.IOException;
@@ -46,16 +47,16 @@ import uws.service.log.UWSLog;
  * write a given error in the best appropriate format. This format is chosen thanks to the "Accept" header of the HTTP request.
  * If no request is provided or if there is no known format, the HTML format is chosen by default.</p>
  * 
- * @author Gr&eacute;gory Mantelet (CDS)
- * @version 06/2012
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 2.0 (07/2014)
  * 
  * @see AbstractServiceErrorWriter
  */
 public class DefaultTAPErrorWriter extends AbstractServiceErrorWriter {
 
-	protected final ServiceConnection<?> service;
+	protected final ServiceConnection service;
 
-	public DefaultTAPErrorWriter(final ServiceConnection<?> service){
+	public DefaultTAPErrorWriter(final ServiceConnection service){
 		this.service = service;
 	}
 
diff --git a/src/tap/formatter/JSONFormat.java b/src/tap/formatter/JSONFormat.java
index 431ee9861e1586505d40cd87587ec4032bc8a759..974df0546c6108419ef1489066e7d37d842d039f 100644
--- a/src/tap/formatter/JSONFormat.java
+++ b/src/tap/formatter/JSONFormat.java
@@ -16,7 +16,8 @@ package tap.formatter;
  * 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 2012 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomisches Rechen Institut (ARI)
  */
 
 import java.io.IOException;
@@ -26,70 +27,107 @@ import java.io.PrintWriter;
 import org.json.JSONException;
 import org.json.JSONWriter;
 
-import cds.savot.writer.SavotWriter;
-import adql.db.DBColumn;
 import tap.ServiceConnection;
 import tap.TAPException;
 import tap.TAPExecutionReport;
+import tap.data.TableIterator;
 import tap.metadata.TAPColumn;
-import tap.metadata.TAPTypes;
+import tap.metadata.TAPType;
+import tap.metadata.TAPType.TAPDatatype;
+import tap.metadata.VotType;
+import adql.db.DBColumn;
 
-public abstract class JSONFormat< R > implements OutputFormat<R> {
+/**
+ * Format any given query (table) result into JSON.
+ * 
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 2.0 (07/2014)
+ */
+public class JSONFormat implements OutputFormat {
 
 	/** Indicates whether a format report (start and end date/time) must be printed in the log output.  */
 	private boolean logFormatReport;
 
 	/** The {@link ServiceConnection} to use (for the log and to have some information about the service (particularly: name, description). */
-	protected final ServiceConnection<R> service;
+	protected final ServiceConnection service;
 
-	public JSONFormat(final ServiceConnection<R> service){
+	/**
+	 * <p>Build a JSON formatter.</p>
+	 * 
+	 * <p><i>note: The built formatter will not write a log entry each time a result is written.
+	 * However if you want this behavior you must you {@link #JSONFormat(ServiceConnection, boolean)}.</i></p>
+	 * 
+	 * @param service	Description of the TAP service.
+	 */
+	public JSONFormat(final ServiceConnection service){
 		this(service, false);
 	}
 
-	public JSONFormat(final ServiceConnection<R> service, final boolean logFormatReport){
+	/**
+	 * Build a JSON formatter.
+	 * 
+	 * @param service			Description of the TAP service.
+	 * @param logFormatReport	<i>true</i> to write a log entry (with nb rows and columns + writing duration) each time a result is written, <i>false</i> otherwise.
+	 */
+	public JSONFormat(final ServiceConnection service, final boolean logFormatReport){
 		this.service = service;
 		this.logFormatReport = logFormatReport;
 	}
 
+	@Override
 	public String getMimeType(){
 		return "application/json";
 	}
 
+	@Override
 	public String getShortMimeType(){
 		return "json";
 	}
 
+	@Override
 	public String getDescription(){
 		return null;
 	}
 
+	@Override
 	public String getFileExtension(){
 		return "json";
 	}
 
 	@Override
-	public void writeResult(R queryResult, OutputStream output, TAPExecutionReport execReport, Thread thread) throws TAPException, InterruptedException{
+	public void writeResult(TableIterator result, OutputStream output, TAPExecutionReport execReport, Thread thread) throws TAPException, InterruptedException{
 		try{
 			long start = System.currentTimeMillis();
 
+			// Prepare the output stream for JSON:
 			PrintWriter writer = new PrintWriter(output);
 			JSONWriter out = new JSONWriter(writer);
 
+			// {
 			out.object();
 
+			// "metadata": {...}
 			out.key("metadata");
-			DBColumn[] columns = writeMetadata(queryResult, out, execReport, thread);
+
+			// Write metadata part:
+			DBColumn[] columns = writeMetadata(result, out, execReport, thread);
 
 			writer.flush();
 
+			// "data": {...}
 			out.key("data");
-			int nbRows = writeData(queryResult, columns, out, execReport, thread);
 
+			// Write the data part:
+			int nbRows = writeData(result, columns, out, execReport, thread);
+
+			// }
 			out.endObject();
 			writer.flush();
 
+			// Report stats about the result writing:
 			if (logFormatReport)
 				service.getLogger().info("JOB " + execReport.jobID + " WRITTEN\tResult formatted (in JSON ; " + nbRows + " rows ; " + columns.length + " columns) in " + (System.currentTimeMillis() - start) + " ms !");
+
 		}catch(JSONException je){
 			throw new TAPException("Error while writing a query result in JSON !", je);
 		}catch(IOException ioe){
@@ -97,59 +135,185 @@ public abstract class JSONFormat< R > implements OutputFormat<R> {
 		}
 	}
 
-	protected abstract DBColumn[] writeMetadata(R queryResult, JSONWriter out, TAPExecutionReport execReport, Thread thread) throws IOException, TAPException, InterruptedException, JSONException;
+	/**
+	 * Write the whole metadata part of the JSON file.
+	 * 
+	 * @param result		Result to write later (but it contains also metadata that was extracted from the result itself).
+	 * @param out			Output stream in which the metadata must be written.
+	 * @param execReport	Execution report (which contains the metadata extracted/guessed from the ADQL query).
+	 * @param thread		Thread which has asked for this formatting (it must be used in order to test the {@link Thread#isInterrupted()} flag and so interrupt everything if need).
+	 * 
+	 * @return	All the written metadata.
+	 * 
+	 * @throws IOException				If there is an error while writing something in the output stream.
+	 * @throws InterruptedException		If the thread has been interrupted.
+	 * @throws JSONException			If there is an error while formatting something in JSON.
+	 * @throws TAPException				If any other error occurs.
+	 * 
+	 * @see #getValidColMeta(DBColumn, TAPColumn)
+	 */
+	protected DBColumn[] writeMetadata(TableIterator result, JSONWriter out, TAPExecutionReport execReport, Thread thread) throws IOException, TAPException, InterruptedException, JSONException{
+		out.array();
+
+		// Get the metadata extracted/guesses from the ADQL query:
+		DBColumn[] columnsFromQuery = execReport.resultingColumns;
+
+		// Get the metadata extracted from the result:
+		TAPColumn[] columnsFromResult = result.getMetadata();
+
+		int indField = 0;
+		if (columnsFromQuery != null){
+
+			// For each column:
+			for(DBColumn field : columnsFromQuery){
+
+				// Try to build/get appropriate metadata for this field/column:
+				TAPColumn colFromResult = (columnsFromResult != null && indField < columnsFromResult.length) ? columnsFromResult[indField] : null;
+				TAPColumn tapCol = getValidColMeta(field, colFromResult);
+
+				// Ensure these metadata are well returned at the end of this function:
+				columnsFromQuery[indField] = tapCol;
+
+				// Write the field/column metadata in the JSON output:
+				writeFieldMeta(tapCol, out);
+				indField++;
+
+				if (thread.isInterrupted())
+					throw new InterruptedException();
+			}
+		}
+
+		out.endArray();
+		return columnsFromQuery;
+	}
 
 	/**
-	 * <p>Formats in a VOTable field and writes the given {@link TAPColumn} in the given Writer.</p>
+	 * Try to get or otherwise to build appropriate metadata using those extracted from the ADQL query and those extracted from the result.
+	 * 
+	 * @param typeFromQuery		Metadata extracted/guessed from the ADQL query.
+	 * @param typeFromResult	Metadata extracted/guessed from the result.
 	 * 
-	 * <p><i><u>Note:</u> If the VOTable datatype is <code>int</code>, <code>short</code> or <code>long</code> a NULL values is set by adding a node VALUES: &lt;VALUES null="..." /&gt;</i></p>
+	 * @return	The most appropriate metadata.
+	 */
+	protected TAPColumn getValidColMeta(final DBColumn typeFromQuery, final TAPColumn typeFromResult){
+		if (typeFromQuery != null && typeFromQuery instanceof TAPColumn)
+			return (TAPColumn)typeFromQuery;
+		else if (typeFromResult != null){
+			if (typeFromQuery != null)
+				return (TAPColumn)typeFromResult.copy(typeFromQuery.getDBName(), typeFromQuery.getADQLName(), null);
+			else
+				return (TAPColumn)typeFromResult.copy();
+		}else
+			return new TAPColumn((typeFromQuery != null) ? typeFromQuery.getADQLName() : "?", new TAPType(TAPDatatype.VARCHAR), "?");
+	}
+
+	/**
+	 * Formats in JSON and writes the given {@link TAPColumn} in the given output.
 	 * 
-	 * @param col				The column metadata to format into a VOTable field.
+	 * @param col				The column metadata to format/write in JSON.
 	 * @param out				The stream in which the formatted column metadata must be written.
 	 * 
 	 * @throws IOException		If there is an error while writing the field metadata.
+	 * @throws JSONException	If there is an error while formatting something in JSON format.
 	 * @throws TAPException		If there is any other error (by default: never happen).
 	 */
 	protected void writeFieldMeta(TAPColumn tapCol, JSONWriter out) throws IOException, TAPException, JSONException{
+		// {
 		out.object();
 
+		// "name": "..."
 		out.key("name").value(tapCol.getName());
 
+		// "description": "..." (if any)
 		if (tapCol.getDescription() != null && tapCol.getDescription().trim().length() > 0)
 			out.key("description").value(tapCol.getDescription());
 
-		out.key("datatype").value(tapCol.getVotType().datatype);
+		// "datatype": "..."
+		VotType votType = tapCol.getVotType();
+		out.key("datatype").value(votType.datatype);
 
-		int arraysize = tapCol.getVotType().arraysize;
-		if (arraysize == TAPTypes.STAR_SIZE)
-			out.key("arraysize").value("*");
-		else if (arraysize > 0)
-			out.key("arraysize").value(arraysize);
+		// "arraysize": "..." (if any)
+		if (votType.unlimitedArraysize){
+			if (votType.arraysize > 0)
+				out.key("arraysize").value(votType.arraysize + "*");
+			else
+				out.key("arraysize").value("*");
+		}else if (votType.arraysize > 0)
+			out.key("arraysize").value(votType.arraysize);
 
-		if (tapCol.getVotType().xtype != null)
-			out.key("xtype").value(tapCol.getVotType().xtype);
+		// "xtype": "..." (if any)
+		if (votType.xtype != null)
+			out.key("xtype").value(votType.xtype);
 
+		// "unit": "..." (if any)
 		if (tapCol.getUnit() != null && tapCol.getUnit().length() > 0)
 			out.key("unit").value(tapCol.getUnit());
 
+		// "ucd": "..." (if any)
 		if (tapCol.getUcd() != null && tapCol.getUcd().length() > 0)
 			out.key("ucd").value(tapCol.getUcd());
 
+		// "utype": "..." (if any)
 		if (tapCol.getUtype() != null && tapCol.getUtype().length() > 0)
 			out.key("utype").value(tapCol.getUtype());
 
+		// }
 		out.endObject();
 	}
 
-	protected abstract int writeData(R queryResult, DBColumn[] selectedColumns, JSONWriter out, TAPExecutionReport execReport, Thread thread) throws IOException, TAPException, InterruptedException, JSONException;
+	/**
+	 * Write the whole data part of the JSON file.
+	 * 
+	 * @param result			Result to write.	
+	 * @param selectedColumns	All columns' metadata.
+	 * @param out				Output stream in which the data must be written.
+	 * @param execReport		Execution report (which contains the maximum allowed number of records to output).
+	 * @param thread			Thread which has asked for this formatting (it must be used in order to test the {@link Thread#isInterrupted()} flag and so interrupt everything if need).
+	 * 
+	 * @return	The number of written result rows. (<i>note: if this number is greater than the value of MAXREC: OVERFLOW</i>)
+	 * 
+	 * @throws IOException				If there is an error while writing something in the output stream.
+	 * @throws InterruptedException		If the thread has been interrupted.
+	 * @throws JSONException			If there is an error while formatting something in JSON.
+	 * @throws TAPException				If any other error occurs.
+	 */
+	protected int writeData(TableIterator result, DBColumn[] selectedColumns, JSONWriter out, TAPExecutionReport execReport, Thread thread) throws IOException, TAPException, InterruptedException, JSONException{
+		// [
+		out.array();
+
+		int nbRows = 0;
+		while(result.nextRow()){
+			// Deal with OVERFLOW, if needed:
+			if (execReport.parameters.getMaxRec() > 0 && nbRows >= execReport.parameters.getMaxRec())
+				break;
+
+			// [
+			out.array();
+			int indCol = 0;
+			while(result.hasNextCol()){
+				// ...
+				writeFieldValue(result.nextCol(), selectedColumns[indCol++], out);
+
+				if (thread.isInterrupted())
+					throw new InterruptedException();
+			}
+			// ]
+			out.endArray();
+			nbRows++;
+
+			if (thread.isInterrupted())
+				throw new InterruptedException();
+		}
+
+		// ]
+		out.endArray();
+		return nbRows;
+	}
 
 	/**
-	 * <p>Writes the given field value in the given OutputStream.</p>
+	 * <p>Writes the given field value in JSON and into the given output.</p>
 	 * 
-	 * <p>
-	 * 	The given value will be encoded as an XML element (see {@link SavotWriter#encodeElement(String)}.
-	 * 	Besides, if the given value is <code>null</code> and if the column datatype is <code>int</code>,
-	 * 	<code>short</code> or <code>long</code>, the NULL values declared in the field metadata will be written.</p>
+	 * <p><i>note: special numeric values NaN and Inf (double or float) will be written as NULL values.</i></p>
 	 * 
 	 * @param value				The value to write.
 	 * @param column			The corresponding column metadata.
diff --git a/src/tap/formatter/OutputFormat.java b/src/tap/formatter/OutputFormat.java
index 13fa7c22bf77f3efcd09b80fbda9e092bb407e3a..a13383a1900eb7b6fd62f555b4b149c8091fbee8 100644
--- a/src/tap/formatter/OutputFormat.java
+++ b/src/tap/formatter/OutputFormat.java
@@ -16,26 +16,24 @@ package tap.formatter;
  * 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 2012 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ *                       Astronomisches Rechen Institut (ARI)
  */
 
 import java.io.OutputStream;
 
 import tap.TAPException;
 import tap.TAPExecutionReport;
+import tap.data.TableIterator;
 
 /**
  * Describes an output format and formats a given query result into this format.
  * 
- * @author Gr&eacute;gory Mantelet (CDS)
- *
- * @param <R>	The type of raw query result (i.e. {@link java.sql.ResultSet}).
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
  * 
- * @version 06/2012
- * 
- * @see VOTableFormat
+ * @version 2.0 (07/2014)
  */
-public interface OutputFormat< R > {
+public interface OutputFormat {
 
 	/**
 	 * Gets the MIME type corresponding to this format.
@@ -68,25 +66,13 @@ public interface OutputFormat< R > {
 	/**
 	 * Formats the given query result and writes it in the given output stream.
 	 * 
-	 * @param queryResult		The raw result to format (i.e. a {@link java.sql.ResultSet}).
-	 * @param output			The output stream (a ServletOutputStream or a stream on a file) in which the formatted result must be written.
-	 * @param execReport		The report of the execution of the TAP query whose the result must be now written.
-	 * @param thread			The thread which has asked the result writting.
+	 * @param result		The raw (table) result to format.
+	 * @param output		The output stream (a ServletOutputStream or a stream on a file) in which the formatted result must be written.
+	 * @param execReport	The report of the execution of the TAP query whose the result must be now written.
+	 * @param thread		The thread which has asked the result writing.
 	 * 
 	 * @throws TAPException		If there is an error while formatting/writing the query result.
 	 */
-	public void writeResult(final R queryResult, final OutputStream output, final TAPExecutionReport execReport, final Thread thread) throws TAPException, InterruptedException;
-
-	/*
-	 * Formats the given query result and writes it in some way accessible through the returned {@link Result}.
-	 * 
-	 * @param queryResult		The raw result to format (i.e. a {@link java.sql.ResultSet}).
-	 * @param job				The job which processed the query.
-	 * 
-	 * @return					The {@link Result} which provides an access to the formatted query result.
-	 * 
-	 * @throws TAPException		If there is an error while formatting/writing the query result.
-	 *
-	public Result writeResult(final R queryResult, final TAPJob job) throws TAPException;*/
+	public void writeResult(final TableIterator result, final OutputStream output, final TAPExecutionReport execReport, final Thread thread) throws TAPException, InterruptedException;
 
 }
diff --git a/src/tap/formatter/ResultSet2JsonFormatter.java b/src/tap/formatter/ResultSet2JsonFormatter.java
deleted file mode 100644
index 2d3561bf4203c92f0876a24fcb8abd1673624fe2..0000000000000000000000000000000000000000
--- a/src/tap/formatter/ResultSet2JsonFormatter.java
+++ /dev/null
@@ -1,118 +0,0 @@
-package tap.formatter;
-
-/*
- * 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 2012 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
- */
-
-import java.io.IOException;
-import java.sql.ResultSet;
-import java.sql.ResultSetMetaData;
-import java.sql.SQLException;
-
-import org.json.JSONException;
-import org.json.JSONWriter;
-
-import tap.ServiceConnection;
-import tap.TAPException;
-import tap.TAPExecutionReport;
-import tap.metadata.TAPColumn;
-import tap.metadata.TAPTypes;
-import adql.db.DBColumn;
-
-public class ResultSet2JsonFormatter extends JSONFormat<ResultSet> implements ResultSetFormatter {
-
-	public ResultSet2JsonFormatter(ServiceConnection<ResultSet> service, boolean logFormatReport){
-		super(service, logFormatReport);
-	}
-
-	public ResultSet2JsonFormatter(ServiceConnection<ResultSet> service){
-		super(service);
-	}
-
-	@Override
-	protected DBColumn[] writeMetadata(ResultSet queryResult, JSONWriter out, TAPExecutionReport execReport, Thread thread) throws IOException, TAPException, InterruptedException, JSONException{
-		out.array();
-		DBColumn[] selectedColumns = execReport.resultingColumns;
-
-		try{
-			ResultSetMetaData meta = queryResult.getMetaData();
-			int indField = 1;
-			if (selectedColumns != null){
-				for(DBColumn field : selectedColumns){
-					TAPColumn tapCol = null;
-					try{
-						tapCol = (TAPColumn)field;
-					}catch(ClassCastException ex){
-						tapCol = new TAPColumn(field.getADQLName());
-						tapCol.setDatatype(meta.getColumnTypeName(indField), TAPTypes.NO_SIZE);
-						service.getLogger().warning("Unknown DB datatype for the field \"" + tapCol.getName() + "\" ! It is supposed to be \"" + tapCol.getDatatype() + "\" (original value: \"" + meta.getColumnTypeName(indField) + "\").");
-						selectedColumns[indField - 1] = tapCol;
-					}
-					writeFieldMeta(tapCol, out);
-					indField++;
-
-					if (thread.isInterrupted())
-						throw new InterruptedException();
-				}
-			}
-		}catch(SQLException e){
-			service.getLogger().error("Job N°" + execReport.jobID + " - Impossible to get the metadata of the given ResultSet !", e);
-		}
-
-		out.endArray();
-		return selectedColumns;
-	}
-
-	@Override
-	protected int writeData(ResultSet queryResult, DBColumn[] selectedColumns, JSONWriter out, TAPExecutionReport execReport, Thread thread) throws IOException, TAPException, InterruptedException, JSONException{
-		out.array();
-		int nbRows = 0;
-		try{
-			int nbColumns = queryResult.getMetaData().getColumnCount();
-			while(queryResult.next()){
-				if (execReport.parameters.getMaxRec() > 0 && nbRows >= execReport.parameters.getMaxRec()) // that's to say: OVERFLOW !
-					break;
-
-				out.array();
-				Object value;
-				for(int i = 1; i <= nbColumns; i++){
-					value = formatValue(queryResult.getObject(i), selectedColumns[i - 1]);
-					writeFieldValue(value, selectedColumns[i - 1], out);
-					if (thread.isInterrupted())
-						throw new InterruptedException();
-				}
-				out.endArray();
-				nbRows++;
-
-				if (thread.isInterrupted())
-					throw new InterruptedException();
-			}
-		}catch(SQLException se){
-			throw new TAPException("Job N°" + execReport.jobID + " - Impossible to get the " + (nbRows + 1) + "-th rows from the given ResultSet !", se);
-		}
-
-		out.endArray();
-		return nbRows;
-	}
-
-	@Override
-	public Object formatValue(Object value, DBColumn colMeta){
-		return value;
-	}
-
-}
diff --git a/src/tap/formatter/ResultSet2SVFormatter.java b/src/tap/formatter/ResultSet2SVFormatter.java
deleted file mode 100644
index 628957071ee609e48f2db64d99bfde961803610d..0000000000000000000000000000000000000000
--- a/src/tap/formatter/ResultSet2SVFormatter.java
+++ /dev/null
@@ -1,105 +0,0 @@
-package tap.formatter;
-
-/*
- * 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 2012 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
- */
-
-import java.io.IOException;
-import java.io.PrintWriter;
-
-import java.sql.ResultSet;
-import java.sql.SQLException;
-
-import adql.db.DBColumn;
-
-import tap.ServiceConnection;
-import tap.TAPException;
-import tap.TAPExecutionReport;
-
-public class ResultSet2SVFormatter extends SVFormat<ResultSet> implements ResultSetFormatter {
-
-	public ResultSet2SVFormatter(final ServiceConnection<ResultSet> service, char colSeparator, boolean delimitStrings){
-		super(service, colSeparator, delimitStrings);
-	}
-
-	public ResultSet2SVFormatter(final ServiceConnection<ResultSet> service, char colSeparator){
-		super(service, colSeparator);
-	}
-
-	public ResultSet2SVFormatter(final ServiceConnection<ResultSet> service, String colSeparator, boolean delimitStrings){
-		super(service, colSeparator, delimitStrings);
-	}
-
-	public ResultSet2SVFormatter(final ServiceConnection<ResultSet> service, String colSeparator){
-		super(service, colSeparator);
-	}
-
-	@Override
-	protected DBColumn[] writeMetadata(ResultSet queryResult, PrintWriter writer, TAPExecutionReport execReport, Thread thread) throws IOException, TAPException, InterruptedException{
-		DBColumn[] selectedColumns = execReport.resultingColumns;
-		int nbColumns = (selectedColumns == null) ? -1 : selectedColumns.length;
-		if (nbColumns > 0){
-			for(int i = 0; i < nbColumns - 1; i++){
-				writer.print(selectedColumns[i].getADQLName());
-				writer.print(separator);
-			}
-			writer.print(selectedColumns[nbColumns - 1].getADQLName());
-			writer.println();
-			writer.flush();
-		}
-		return selectedColumns;
-	}
-
-	@Override
-	protected int writeData(ResultSet queryResult, DBColumn[] selectedColumns, PrintWriter writer, TAPExecutionReport execReport, Thread thread) throws IOException, TAPException, InterruptedException{
-		int nbRows = 0;
-		try{
-			int nbColumns = queryResult.getMetaData().getColumnCount();
-			while(queryResult.next()){
-				if (execReport.parameters.getMaxRec() > 0 && nbRows >= execReport.parameters.getMaxRec()) // that's to say: OVERFLOW !
-					break;
-
-				Object value;
-				for(int i = 1; i <= nbColumns; i++){
-					value = formatValue(queryResult.getObject(i), selectedColumns[i - 1]);
-					writeFieldValue(value, selectedColumns[i - 1], writer);
-					if (i != nbColumns)
-						writer.print(separator);
-					if (thread.isInterrupted())
-						throw new InterruptedException();
-				}
-				writer.println();
-				nbRows++;
-
-				if (thread.isInterrupted())
-					throw new InterruptedException();
-			}
-			writer.flush();
-		}catch(SQLException se){
-			throw new TAPException("Job N°" + execReport.jobID + " - Impossible to get the " + (nbRows + 1) + "-th rows from the given ResultSet !", se);
-		}
-
-		return nbRows;
-	}
-
-	@Override
-	public Object formatValue(Object value, DBColumn colMeta){
-		return value;
-	}
-
-}
diff --git a/src/tap/formatter/ResultSet2TextFormatter.java b/src/tap/formatter/ResultSet2TextFormatter.java
deleted file mode 100644
index 045bf804ec630856dc7032f3a832fb4de844e1de..0000000000000000000000000000000000000000
--- a/src/tap/formatter/ResultSet2TextFormatter.java
+++ /dev/null
@@ -1,85 +0,0 @@
-package tap.formatter;
-
-/*
- * 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 2012 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
- */
-
-import java.sql.ResultSet;
-import java.sql.SQLException;
-
-import adql.db.DBColumn;
-
-import cds.util.AsciiTable;
-
-import tap.ServiceConnection;
-import tap.TAPException;
-import tap.TAPExecutionReport;
-
-public class ResultSet2TextFormatter extends TextFormat<ResultSet> implements ResultSetFormatter {
-
-	public ResultSet2TextFormatter(ServiceConnection<ResultSet> service){
-		super(service);
-	}
-
-	@Override
-	protected String getHeader(ResultSet queryResult, TAPExecutionReport execReport, Thread thread) throws TAPException{
-		DBColumn[] selectedColumns = execReport.resultingColumns;
-		StringBuffer line = new StringBuffer();
-		int nbColumns = (selectedColumns == null) ? -1 : selectedColumns.length;
-		if (nbColumns > 0){
-			for(int i = 0; i < nbColumns - 1; i++)
-				line.append(selectedColumns[i].getADQLName()).append('|');
-			line.append(selectedColumns[nbColumns - 1].getADQLName());
-		}
-		return line.toString();
-	}
-
-	@Override
-	protected int writeData(ResultSet queryResult, AsciiTable asciiTable, TAPExecutionReport execReport, Thread thread) throws TAPException{
-		int nbRows = 0;
-		try{
-			DBColumn[] selectedColumns = execReport.resultingColumns;
-			int nbColumns = selectedColumns.length;
-			StringBuffer line = new StringBuffer();
-			while(queryResult.next()){
-				if (execReport.parameters.getMaxRec() > 0 && nbRows >= execReport.parameters.getMaxRec()) // that's to say: OVERFLOW !
-					break;
-
-				line.delete(0, line.length());
-				Object value;
-				for(int i = 1; i <= nbColumns; i++){
-					value = formatValue(queryResult.getObject(i), selectedColumns[i - 1]);
-					writeFieldValue(value, selectedColumns[i - 1], line);
-					if (i != nbColumns)
-						line.append('|');
-				}
-				asciiTable.addLine(line.toString());
-				nbRows++;
-			}
-		}catch(SQLException se){
-			throw new TAPException("Job N°" + execReport.jobID + " - Impossible to get the " + (nbRows + 1) + "-th rows from the given ResultSet !", se);
-		}
-		return nbRows;
-	}
-
-	@Override
-	public Object formatValue(Object value, DBColumn colMeta){
-		return value;
-	}
-
-}
diff --git a/src/tap/formatter/ResultSet2VotableFormatter.java b/src/tap/formatter/ResultSet2VotableFormatter.java
deleted file mode 100644
index 2452e6fe6b568b5218d661c80bb6f4c326a785a1..0000000000000000000000000000000000000000
--- a/src/tap/formatter/ResultSet2VotableFormatter.java
+++ /dev/null
@@ -1,125 +0,0 @@
-package tap.formatter;
-
-/*
- * 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 2012 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
- */
-
-import java.io.IOException;
-
-import tap.TAPExecutionReport;
-import tap.TAPException;
-
-import java.io.OutputStream;
-import java.io.PrintWriter;
-import java.sql.ResultSet;
-import java.sql.ResultSetMetaData;
-import java.sql.SQLException;
-
-import tap.ServiceConnection;
-import tap.metadata.TAPColumn;
-import tap.metadata.TAPTypes;
-import adql.db.DBColumn;
-
-/**
- * Formats a {@link ResultSet} into a VOTable.
- * 
- * @author Gr&eacute;gory Mantelet (CDS)
- * @version 11/2011
- */
-public class ResultSet2VotableFormatter extends VOTableFormat<ResultSet> implements ResultSetFormatter {
-
-	public ResultSet2VotableFormatter(final ServiceConnection<ResultSet> service) throws NullPointerException{
-		super(service);
-	}
-
-	public ResultSet2VotableFormatter(final ServiceConnection<ResultSet> service, final boolean logFormatReport) throws NullPointerException{
-		super(service, logFormatReport);
-	}
-
-	@Override
-	protected DBColumn[] writeMetadata(final ResultSet queryResult, final PrintWriter output, final TAPExecutionReport execReport, final Thread thread) throws IOException, TAPException, InterruptedException{
-		DBColumn[] selectedColumns = execReport.resultingColumns;
-		try{
-			ResultSetMetaData meta = queryResult.getMetaData();
-			int indField = 1;
-			if (selectedColumns != null){
-				for(DBColumn field : selectedColumns){
-					TAPColumn tapCol = null;
-					try{
-						tapCol = (TAPColumn)field;
-					}catch(ClassCastException ex){
-						tapCol = new TAPColumn(field.getADQLName());
-						tapCol.setDatatype(meta.getColumnTypeName(indField), TAPTypes.NO_SIZE);
-						service.getLogger().warning("Unknown DB datatype for the field \"" + tapCol.getName() + "\" ! It is supposed to be \"" + tapCol.getDatatype() + "\" (original value: \"" + meta.getColumnTypeName(indField) + "\").");
-						selectedColumns[indField - 1] = tapCol;
-					}
-					writeFieldMeta(tapCol, output);
-					indField++;
-
-					if (thread.isInterrupted())
-						throw new InterruptedException();
-				}
-			}
-		}catch(SQLException e){
-			service.getLogger().error("Job N°" + execReport.jobID + " - Impossible to get the metadata of the given ResultSet !", e);
-			output.println("<INFO name=\"WARNING\" value=\"MISSING_META\">Error while getting field(s) metadata</INFO>");
-		}
-		return selectedColumns;
-	}
-
-	@Override
-	protected int writeData(final ResultSet queryResult, final DBColumn[] selectedColumns, final OutputStream output, final TAPExecutionReport execReport, final Thread thread) throws IOException, TAPException, InterruptedException{
-		int nbRows = 0;
-		try{
-			output.write("\t\t\t\t<TABLEDATA>\n".getBytes());
-			int nbColumns = queryResult.getMetaData().getColumnCount();
-			while(queryResult.next()){
-				if (execReport.parameters.getMaxRec() > 0 && nbRows >= execReport.parameters.getMaxRec())
-					break;
-
-				output.write("\t\t\t\t\t<TR>\n".getBytes());
-				Object value;
-				for(int i = 1; i <= nbColumns; i++){
-					output.write("\t\t\t\t\t\t<TD>".getBytes());
-					value = formatValue(queryResult.getObject(i), selectedColumns[i - 1]);
-					writeFieldValue(value, selectedColumns[i - 1], output);
-					output.write("</TD>\n".getBytes());
-
-					if (thread.isInterrupted())
-						throw new InterruptedException();
-				}
-
-				output.write("\t\t\t\t\t</TR>\n".getBytes());
-				nbRows++;
-
-				if (thread.isInterrupted())
-					throw new InterruptedException();
-			}
-			output.write("\t\t\t\t</TABLEDATA>\n".getBytes());
-			return nbRows;
-		}catch(SQLException e){
-			throw new TAPException("Job N°" + execReport.jobID + " - Impossible to get the " + (nbRows + 1) + "-th rows from the given ResultSet !", e);
-		}
-	}
-
-	@Override
-	public Object formatValue(Object value, DBColumn colMeta){
-		return value;
-	}
-
-}
diff --git a/src/tap/formatter/ResultSetFormatter.java b/src/tap/formatter/ResultSetFormatter.java
deleted file mode 100644
index bd4608d7cb42c4588ddb1605106c90689e84af66..0000000000000000000000000000000000000000
--- a/src/tap/formatter/ResultSetFormatter.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package tap.formatter;
-
-/*
- * 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 2012 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
- */
-
-import java.sql.ResultSet;
-
-import tap.TAPException;
-
-import adql.db.DBColumn;
-
-public interface ResultSetFormatter extends OutputFormat<ResultSet> {
-
-	public Object formatValue(final Object value, final DBColumn colMeta) throws TAPException;
-
-}
diff --git a/src/tap/formatter/SVFormat.java b/src/tap/formatter/SVFormat.java
index 259fff2ef5129a7f1013c079edd7080c4eed363c..81e25c10eb3b189709f5b5d8fa9fd425e8400f38 100644
--- a/src/tap/formatter/SVFormat.java
+++ b/src/tap/formatter/SVFormat.java
@@ -16,58 +16,107 @@ package tap.formatter;
  * 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 2012 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomisches Rechen Institut (ARI)
  */
 
 import java.io.IOException;
 import java.io.OutputStream;
 import java.io.PrintWriter;
 
-import cds.savot.writer.SavotWriter;
-import adql.db.DBColumn;
 import tap.ServiceConnection;
 import tap.TAPException;
 import tap.TAPExecutionReport;
+import tap.data.TableIterator;
+import adql.db.DBColumn;
 
-public abstract class SVFormat< R > implements OutputFormat<R> {
+/**
+ * Format any given query (table) result into CSV or TSV (or with custom separator).
+ * 
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 2.0 (07/2014)
+ */
+public class SVFormat implements OutputFormat {
 
 	/** Indicates whether a format report (start and end date/time) must be printed in the log output.  */
 	private boolean logFormatReport;
 
+	/** Column separator for CSV format. */
 	public static final char COMMA_SEPARATOR = ',';
+	/** Column separator for sCSV format. */
 	public static final char SEMI_COLON_SEPARATOR = ';';
+	/** Column separator for TSV format. */
 	public static final char TAB_SEPARATOR = '\t';
 
-	protected final ServiceConnection<R> service;
+	/** The {@link ServiceConnection} to use (for the log and to have some information about the service (particularly: name, description). */
+	protected final ServiceConnection service;
 
+	/** Column separator to use. */
 	protected final String separator;
+
+	/** Indicate whether String values must be delimited by double quotes (default) or not. */
 	protected final boolean delimitStr;
 
-	public SVFormat(final ServiceConnection<R> service, char colSeparator){
+	/**
+	 * Build a SVFormat (in which String values are delimited by double quotes).
+	 * 
+	 * @param service		Description of the TAP service.
+	 * @param colSeparator	Column separator to use.
+	 */
+	public SVFormat(final ServiceConnection service, char colSeparator){
 		this(service, colSeparator, true);
 	}
 
-	public SVFormat(final ServiceConnection<R> service, char colSeparator, boolean delimitStrings){
+	/**
+	 * Build a SVFormat.
+	 * 
+	 * @param service			Description of the TAP service.
+	 * @param colSeparator		Column separator to use.
+	 * @param delimitStrings	<i>true</i> if String values must be delimited by double quotes, <i>false</i> otherwise.
+	 */
+	public SVFormat(final ServiceConnection service, char colSeparator, boolean delimitStrings){
 		this(service, colSeparator, delimitStrings, false);
 	}
 
-	public SVFormat(final ServiceConnection<R> service, char colSeparator, boolean delimitStrings, final boolean logFormatReport){
+	/**
+	 * Build a SVFormat.
+	 * 
+	 * @param service			Description of the TAP service.
+	 * @param colSeparator		Column separator to use.
+	 * @param delimitStrings	<i>true</i> if String values must be delimited by double quotes, <i>false</i> otherwise.
+	 * @param logFormatReport	<i>true</i> to write a log entry (with nb rows and columns + writing duration) each time a result is written, <i>false</i> otherwise.
+	 */
+	public SVFormat(final ServiceConnection service, char colSeparator, boolean delimitStrings, final boolean logFormatReport){
 		separator = "" + colSeparator;
 		delimitStr = delimitStrings;
 		this.service = service;
 		this.logFormatReport = logFormatReport;
 	}
 
-	public SVFormat(final ServiceConnection<R> service, String colSeparator){
+	/**
+	 * Build a SVFormat (in which String values are delimited by double quotes).
+	 * 
+	 * @param service		Description of the TAP service.
+	 * @param colSeparator	Column separator to use.
+	 */
+	public SVFormat(final ServiceConnection service, String colSeparator){
 		this(service, colSeparator, true);
 	}
 
-	public SVFormat(final ServiceConnection<R> service, String colSeparator, boolean delimitStrings){
+	/**
+	 * Build a SVFormat.
+	 * 
+	 * @param service			Description of the TAP service.
+	 * @param colSeparator		Column separator to use.
+	 * @param delimitStrings	<i>true</i> if String values must be delimited by double quotes, <i>false</i> otherwise.
+	 */
+	public SVFormat(final ServiceConnection service, String colSeparator, boolean delimitStrings){
 		separator = (colSeparator == null) ? ("" + COMMA_SEPARATOR) : colSeparator;
 		delimitStr = delimitStrings;
 		this.service = service;
 	}
 
+	@Override
 	public String getMimeType(){
 		switch(separator.charAt(0)){
 			case COMMA_SEPARATOR:
@@ -80,6 +129,7 @@ public abstract class SVFormat< R > implements OutputFormat<R> {
 		}
 	}
 
+	@Override
 	public String getShortMimeType(){
 		switch(separator.charAt(0)){
 			case COMMA_SEPARATOR:
@@ -92,10 +142,12 @@ public abstract class SVFormat< R > implements OutputFormat<R> {
 		}
 	}
 
+	@Override
 	public String getDescription(){
 		return null;
 	}
 
+	@Override
 	public String getFileExtension(){
 		switch(separator.charAt(0)){
 			case COMMA_SEPARATOR:
@@ -109,20 +161,22 @@ public abstract class SVFormat< R > implements OutputFormat<R> {
 	}
 
 	@Override
-	public void writeResult(R queryResult, OutputStream output, TAPExecutionReport execReport, Thread thread) throws TAPException, InterruptedException{
+	public void writeResult(TableIterator result, OutputStream output, TAPExecutionReport execReport, Thread thread) throws TAPException, InterruptedException{
 		try{
 			final long startTime = System.currentTimeMillis();
 
+			// Prepare the output stream:
 			final PrintWriter writer = new PrintWriter(output);
 
 			// Write header:
-			DBColumn[] columns = writeMetadata(queryResult, writer, execReport, thread);
+			DBColumn[] columns = writeHeader(result, writer, execReport, thread);
 
 			// Write data:
-			int nbRows = writeData(queryResult, columns, writer, execReport, thread);
+			int nbRows = writeData(result, columns, writer, execReport, thread);
 
 			writer.flush();
 
+			// Report stats about the result writing:
 			if (logFormatReport)
 				service.getLogger().info("JOB " + execReport.jobID + " WRITTEN\tResult formatted (in SV[" + delimitStr + "] ; " + nbRows + " rows ; " + columns.length + " columns) in " + (System.currentTimeMillis() - startTime) + " ms !");
 
@@ -131,17 +185,96 @@ public abstract class SVFormat< R > implements OutputFormat<R> {
 		}
 	}
 
-	protected abstract DBColumn[] writeMetadata(R queryResult, PrintWriter writer, TAPExecutionReport execReport, Thread thread) throws IOException, TAPException, InterruptedException;
+	/**
+	 * Write the whole header (one row whose columns are just the columns' name).
+	 * 
+	 * @param result		Result to write later (but it contains also metadata that was extracted from the result itself).
+	 * @param writer		Output in which the metadata must be written.
+	 * @param execReport	Execution report (which contains the metadata extracted/guessed from the ADQL query).
+	 * @param thread		Thread which has asked for this formatting (it must be used in order to test the {@link Thread#isInterrupted()} flag and so interrupt everything if need).
+	 * 
+	 * @return	All the written metadata.
+	 * 
+	 * @throws IOException				If there is an error while writing something in the output.
+	 * @throws InterruptedException		If the thread has been interrupted.
+	 * @throws TAPException				If any other error occurs.
+	 */
+	protected DBColumn[] writeHeader(TableIterator result, PrintWriter writer, TAPExecutionReport execReport, Thread thread) throws IOException, TAPException, InterruptedException{
+		// Get the columns meta:
+		DBColumn[] selectedColumns = execReport.resultingColumns;
+
+		// If meta are not known, no header will be written:
+		int nbColumns = (selectedColumns == null) ? -1 : selectedColumns.length;
+		if (nbColumns > 0){
+			// Write all columns' name:
+			for(int i = 0; i < nbColumns - 1; i++){
+				writer.print(selectedColumns[i].getADQLName());
+				writer.print(separator);
+			}
+			writer.print(selectedColumns[nbColumns - 1].getADQLName());
+
+			// Go to a new line (in order to prepare the data writing):
+			writer.println();
+			writer.flush();
+		}
+
+		// Returns the written columns:
+		return selectedColumns;
+	}
+
+	/**
+	 * Write all the data rows.
+	 * 
+	 * @param result			Result to write.	
+	 * @param selectedColumns	All columns' metadata.
+	 * @param out				Output stream in which the data must be written.
+	 * @param execReport		Execution report (which contains the maximum allowed number of records to output).
+	 * @param thread			Thread which has asked for this formatting (it must be used in order to test the {@link Thread#isInterrupted()} flag and so interrupt everything if need).
+	 * 
+	 * @return	The number of written result rows. (<i>note: if this number is greater than the value of MAXREC: OVERFLOW</i>)
+	 * 
+	 * @throws IOException				If there is an error while writing something in the output stream.
+	 * @throws InterruptedException		If the thread has been interrupted.
+	 * @throws TAPException				If any other error occurs.
+	 */
+	protected int writeData(TableIterator result, DBColumn[] selectedColumns, PrintWriter writer, TAPExecutionReport execReport, Thread thread) throws IOException, TAPException, InterruptedException{
+		int nbRows = 0;
+
+		while(result.nextRow()){
+			// Deal with OVERFLOW, if needed:
+			if (execReport.parameters.getMaxRec() > 0 && nbRows >= execReport.parameters.getMaxRec()) // that's to say: OVERFLOW !
+				break;
 
-	protected abstract int writeData(R queryResult, DBColumn[] selectedColumns, PrintWriter writer, TAPExecutionReport execReport, Thread thread) throws IOException, TAPException, InterruptedException;
+			int indCol = 0;
+			while(result.hasNextCol()){
+				// Write the column value:
+				writeFieldValue(result.nextCol(), selectedColumns[indCol++], writer);
+
+				// Append the column separator:
+				if (result.hasNextCol())
+					writer.print(separator);
+
+				if (thread.isInterrupted())
+					throw new InterruptedException();
+			}
+			writer.println();
+			nbRows++;
+
+			if (thread.isInterrupted())
+				throw new InterruptedException();
+		}
+		writer.flush();
+
+		return nbRows;
+	}
 
 	/**
-	 * <p>Writes the given field value in the given OutputStream.</p>
+	 * <p>Writes the given field value in the given PrintWriter.</p>
 	 * 
 	 * <p>
-	 * 	The given value will be encoded as an XML element (see {@link SavotWriter#encodeElement(String)}.
-	 * 	Besides, if the given value is <code>null</code> and if the column datatype is <code>int</code>,
-	 * 	<code>short</code> or <code>long</code>, the NULL values declared in the field metadata will be written.</p>
+	 * 	A String value will be delimited if {@link #delimitStr} is true, otherwise this type of value will
+	 *  be processed like the other type of values: no delimiter and just transformed into a string.
+	 * </p>
 	 * 
 	 * @param value				The value to write.
 	 * @param column			The corresponding column metadata.
diff --git a/src/tap/formatter/TextFormat.java b/src/tap/formatter/TextFormat.java
index 9c5c73486ea6a506e802647ad805b9381c70dba8..35aeb21ec42883aaf6daca92d09d8a74219d4daf 100644
--- a/src/tap/formatter/TextFormat.java
+++ b/src/tap/formatter/TextFormat.java
@@ -16,68 +16,92 @@ package tap.formatter;
  * 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 2012 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomisches Rechen Institut (ARI)
  */
 
+import java.io.IOException;
 import java.io.OutputStream;
 
-import adql.db.DBColumn;
-
-import cds.util.AsciiTable;
-
 import tap.ServiceConnection;
-
 import tap.TAPException;
 import tap.TAPExecutionReport;
+import tap.data.TableIterator;
+import adql.db.DBColumn;
+import cds.util.AsciiTable;
 
-public abstract class TextFormat< R > implements OutputFormat<R> {
+/**
+ * Format any given query (table) result into a simple table ASCII representation
+ * (columns' width are adjusted so that all columns are well aligned and of the same width).
+ * 
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 2.0 (07/2014)
+ */
+public class TextFormat implements OutputFormat {
 
 	/** Indicates whether a format report (start and end date/time) must be printed in the log output.  */
 	private boolean logFormatReport;
 
-	protected final ServiceConnection<R> service;
+	/** The {@link ServiceConnection} to use (for the log and to have some information about the service (particularly: name, description). */
+	protected final ServiceConnection service;
 
-	public TextFormat(final ServiceConnection<R> service){
+	/**
+	 * Build a {@link TextFormat}.
+	 * 
+	 * @param service	Description of the TAP service.
+	 */
+	public TextFormat(final ServiceConnection service){
 		this(service, false);
 	}
 
-	public TextFormat(final ServiceConnection<R> service, final boolean logFormatReport){
+	/**
+	 * Build a {@link TextFormat}.
+	 * 
+	 * @param service			Description of the TAP service.
+	 * @param logFormatReport	<i>true</i> to write a log entry (with nb rows and columns + writing duration) each time a result is written, <i>false</i> otherwise.
+	 */
+	public TextFormat(final ServiceConnection service, final boolean logFormatReport){
 		this.service = service;
 		this.logFormatReport = logFormatReport;
 	}
 
+	@Override
 	public String getMimeType(){
 		return "text/plain";
 	}
 
+	@Override
 	public String getShortMimeType(){
 		return "text";
 	}
 
+	@Override
 	public String getDescription(){
 		return null;
 	}
 
+	@Override
 	public String getFileExtension(){
 		return "txt";
 	}
 
 	@Override
-	public void writeResult(R queryResult, OutputStream output, TAPExecutionReport execReport, Thread thread) throws TAPException, InterruptedException{
+	public void writeResult(TableIterator result, OutputStream output, TAPExecutionReport execReport, Thread thread) throws TAPException, InterruptedException{
 		try{
+			// Prepare the formatting of the whole output:
 			AsciiTable asciiTable = new AsciiTable('|');
 
 			final long startTime = System.currentTimeMillis();
 
 			// Write header:
-			String headerLine = getHeader(queryResult, execReport, thread);
+			String headerLine = getHeader(result, execReport, thread);
 			asciiTable.addHeaderLine(headerLine);
 			asciiTable.endHeaderLine();
 
-			// Write data:
-			int nbRows = writeData(queryResult, asciiTable, execReport, thread);
+			// Write data into the AsciiTable object:
+			int nbRows = writeData(result, asciiTable, execReport, thread);
 
-			// Write all lines in the output stream:
+			// Finally write the formatted ASCII table (header + data) in the output stream:
 			String[] lines = asciiTable.displayAligned(new int[]{AsciiTable.LEFT});
 			for(String l : lines){
 				output.write(l.getBytes());
@@ -85,6 +109,7 @@ public abstract class TextFormat< R > implements OutputFormat<R> {
 			}
 			output.flush();
 
+			// Report stats about the result writing:
 			if (logFormatReport)
 				service.getLogger().info("JOB " + execReport.jobID + " WRITTEN\tResult formatted (in text ; " + nbRows + " rows ; " + ((execReport != null && execReport.resultingColumns != null) ? "?" : execReport.resultingColumns.length) + " columns) in " + (System.currentTimeMillis() - startTime) + " ms !");
 
@@ -93,10 +118,95 @@ public abstract class TextFormat< R > implements OutputFormat<R> {
 		}
 	}
 
-	protected abstract String getHeader(final R queryResult, final TAPExecutionReport execReport, final Thread thread) throws TAPException;
+	/**
+	 * Get the whole header (one row whose columns are just the columns' name).
+	 * 
+	 * @param result		Result to write later (but it contains also metadata that was extracted from the result itself).
+	 * @param writer		Output in which the metadata must be written.
+	 * @param execReport	Execution report (which contains the metadata extracted/guessed from the ADQL query).
+	 * @param thread		Thread which has asked for this formatting (it must be used in order to test the {@link Thread#isInterrupted()} flag and so interrupt everything if need).
+	 * 
+	 * @return	All the written metadata.
+	 * 
+	 * @throws TAPException				If any other error occurs.
+	 */
+	protected String getHeader(final TableIterator result, final TAPExecutionReport execReport, final Thread thread) throws TAPException{
+		// Get the columns meta:
+		DBColumn[] selectedColumns = execReport.resultingColumns;
+
+		StringBuffer line = new StringBuffer();
+
+		// If meta are not known, no header will be written:
+		int nbColumns = (selectedColumns == null) ? -1 : selectedColumns.length;
+		if (nbColumns > 0){
+
+			// Write all columns' name:
+			for(int i = 0; i < nbColumns - 1; i++)
+				line.append(selectedColumns[i].getADQLName()).append('|');
+			line.append(selectedColumns[nbColumns - 1].getADQLName());
+		}
+
+		// Return the header line:
+		return line.toString();
+	}
 
-	protected abstract int writeData(final R queryResult, final AsciiTable asciiTable, final TAPExecutionReport execReport, final Thread thread) throws TAPException;
+	/**
+	 * Write all the data rows into the given {@link AsciiTable} object.
+	 * 
+	 * @param result			Result to write.	
+	 * @param asciiTable		Output in which the rows (as string) must be written.
+	 * @param execReport		Execution report (which contains the maximum allowed number of records to output).
+	 * @param thread			Thread which has asked for this formatting (it must be used in order to test the {@link Thread#isInterrupted()} flag and so interrupt everything if need).
+	 * 
+	 * @return	The number of written result rows. (<i>note: if this number is greater than the value of MAXREC: OVERFLOW</i>)
+	 * 
+	 * @throws IOException				If there is an error while writing something in the output stream.
+	 * @throws InterruptedException		If the thread has been interrupted.
+	 * @throws TAPException				If any other error occurs.
+	 */
+	protected int writeData(final TableIterator queryResult, final AsciiTable asciiTable, final TAPExecutionReport execReport, final Thread thread) throws TAPException{
+		int nbRows = 0;
+
+		// Get the list of columns:
+		DBColumn[] selectedColumns = execReport.resultingColumns;
+		int nbColumns = selectedColumns.length;
+
+		StringBuffer line = new StringBuffer();
+		while(queryResult.nextRow()){
+			// Deal with OVERFLOW, if needed:
+			if (execReport.parameters.getMaxRec() > 0 && nbRows >= execReport.parameters.getMaxRec())
+				break;
+
+			// Clear the line buffer:
+			line.delete(0, line.length());
+
+			int indCol = 0;
+			while(queryResult.hasNextCol()){
+
+				// Write the column value:
+				writeFieldValue(queryResult.nextCol(), selectedColumns[indCol++], line);
+
+				// Write the column separator (if needed):
+				if (indCol != nbColumns)
+					line.append('|');
+			}
+
+			// Append the line/row in the ASCII table:
+			asciiTable.addLine(line.toString());
+
+			nbRows++;
+		}
+
+		return nbRows;
+	}
 
+	/**
+	 * Writes the given field value in the given buffer.
+	 * 
+	 * @param value				The value to write.
+	 * @param tapCol			The corresponding column metadata.
+	 * @param line				The buffer in which the field value must be written.
+	 */
 	protected void writeFieldValue(final Object value, final DBColumn tapCol, final StringBuffer line){
 		Object obj = value;
 		if (obj != null)
diff --git a/src/tap/formatter/VOTableFormat.java b/src/tap/formatter/VOTableFormat.java
index fa3220c1d6fc3528157f166afe14be36bd8513bf..04ca76a0508114add3e6b4d5cabcfe6e4722f78b 100644
--- a/src/tap/formatter/VOTableFormat.java
+++ b/src/tap/formatter/VOTableFormat.java
@@ -16,58 +16,45 @@ package tap.formatter;
  * 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 2012 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ *                       Astronomisches Rechen Institut (ARI)
  */
 
 import java.io.IOException;
-
-import tap.TAPExecutionReport;
-import tap.TAPJob;
-import tap.TAPException;
-import uws.job.Result;
-
 import java.io.OutputStream;
 import java.io.PrintWriter;
 
-import cds.savot.writer.SavotWriter;
 import tap.ServiceConnection;
+import tap.TAPException;
+import tap.TAPExecutionReport;
+import tap.TAPJob;
+import tap.data.TableIterator;
 import tap.metadata.TAPColumn;
+import tap.metadata.TAPType;
+import tap.metadata.TAPType.TAPDatatype;
 import tap.metadata.VotType;
+import tap.metadata.VotType.VotDatatype;
 import adql.db.DBColumn;
+import cds.savot.writer.SavotWriter;
 
 /**
- * <p>Formats the given type of query result in VOTable.</p>
- * <p>
- * 	This abstract class is only able to format the skeleton of the VOTable.
- * 	However, it also provides useful methods to format field metadata and field value (including NULL values).
- * </p>
+ * <p>Format any given query (table) result into VOTable.</p>
  * <p>
  * 	Attributes of the VOTable node are by default set by this class but can be overridden if necessary thanks to the corresponding class attributes:
  * 	{@link #votTableVersion}, {@link #xmlnsXsi}, {@link #xsiNoNamespaceSchemaLocation}, {@link #xsiSchemaLocation} and
  *  {@link #xmlns}.
  * </p>
- * <p>
- *	When overridding this class, you must implement {@link #writeMetadata(Object, PrintWriter, TAPJob)} and
- *	{@link #writeData(Object, DBColumn[], OutputStream, TAPJob)}.
- *	Both are called by {@link #writeResult(Object, OutputStream, TAPJob)}. Finally you will also have to implement
- *	{@link #writeResult(Object, TAPJob)}, which must format the given result into a VOTable saved in some way accessible
- *	through the returned {@link Result}.
- * </p>
- * 
- * @author Gr&eacute;gory Mantelet (CDS)
- * @version 06/2012
- *
- * @param <R>	Type of the result to format in VOTable (i.e. {@link java.sql.ResultSet}).
  * 
- * @see ResultSet2VotableFormatter
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 2.0 (07/2014)
  */
-public abstract class VOTableFormat< R > implements OutputFormat<R> {
+public class VOTableFormat implements OutputFormat {
 
 	/** Indicates whether a format report (start and end date/time) must be printed in the log output.  */
 	private boolean logFormatReport;
 
 	/** The {@link ServiceConnection} to use (for the log and to have some information about the service (particularly: name, description). */
-	protected final ServiceConnection<R> service;
+	protected final ServiceConnection service;
 
 	protected String votTableVersion = "1.2";
 	protected String xmlnsXsi = "http://www.w3.org/2001/XMLSchema-instance";
@@ -84,7 +71,7 @@ public abstract class VOTableFormat< R > implements OutputFormat<R> {
 	 * 
 	 * @see #VOTableFormat(ServiceConnection, boolean)
 	 */
-	public VOTableFormat(final ServiceConnection<R> service) throws NullPointerException{
+	public VOTableFormat(final ServiceConnection service) throws NullPointerException{
 		this(service, false);
 	}
 
@@ -96,25 +83,29 @@ public abstract class VOTableFormat< R > implements OutputFormat<R> {
 	 * 
 	 * @throws NullPointerException	If the given service connection is <code>null</code>.
 	 */
-	public VOTableFormat(final ServiceConnection<R> service, final boolean logFormatReport) throws NullPointerException{
+	public VOTableFormat(final ServiceConnection service, final boolean logFormatReport) throws NullPointerException{
 		if (service == null)
 			throw new NullPointerException("The given service connection is NULL !");
 		this.service = service;
 		this.logFormatReport = logFormatReport;
 	}
 
+	@Override
 	public final String getMimeType(){
 		return "text/xml";
 	}
 
+	@Override
 	public final String getShortMimeType(){
 		return "votable";
 	}
 
+	@Override
 	public String getDescription(){
 		return null;
 	}
 
+	@Override
 	public String getFileExtension(){
 		return "xml";
 	}
@@ -135,7 +126,8 @@ public abstract class VOTableFormat< R > implements OutputFormat<R> {
 	 * 
 	 * @see tap.formatter.OutputFormat#writeResult(Object, OutputStream, TAPExecutionReport)
 	 */
-	public final void writeResult(final R queryResult, final OutputStream output, final TAPExecutionReport execReport, final Thread thread) throws TAPException, InterruptedException{
+	@Override
+	public final void writeResult(final TableIterator queryResult, final OutputStream output, final TAPExecutionReport execReport, final Thread thread) throws TAPException, InterruptedException{
 		try{
 			long start = System.currentTimeMillis();
 
@@ -205,10 +197,10 @@ public abstract class VOTableFormat< R > implements OutputFormat<R> {
 	 * <p>Writes fields' metadata of the given query result in the given Writer.</p>
 	 * <p><b><u>Important:</u> To write write metadata of a given field you can use {@link #writeFieldMeta(TAPColumn, PrintWriter)}.</b></p>
 	 * 
-	 * @param queryResult	The query result from whose fields' metadata must be written.
+	 * @param result		The query result from whose fields' metadata must be written.
 	 * @param output		Writer in which fields' metadata must be written.
 	 * @param execReport	The report of the query execution.
-	 * @param thread		The thread which asked for the result writting.
+	 * @param thread		The thread which asked for the result writing.
 	 * 
 	 * @return				Extracted field's metadata.
 	 * 
@@ -216,7 +208,58 @@ public abstract class VOTableFormat< R > implements OutputFormat<R> {
 	 * @throws TAPException				If there is any other error.
 	 * @throws InterruptedException		If the given thread has been interrupted.
 	 */
-	protected abstract DBColumn[] writeMetadata(final R queryResult, final PrintWriter output, final TAPExecutionReport execReport, final Thread thread) throws IOException, TAPException, InterruptedException;
+	protected DBColumn[] writeMetadata(final TableIterator result, final PrintWriter output, final TAPExecutionReport execReport, final Thread thread) throws IOException, TAPException, InterruptedException{
+		// Get the metadata extracted/guesses from the ADQL query:
+		DBColumn[] columnsFromQuery = execReport.resultingColumns;
+
+		// Get the metadata extracted from the result:
+		TAPColumn[] columnsFromResult = result.getMetadata();
+
+		int indField = 0;
+		if (columnsFromQuery != null){
+
+			// For each column:
+			for(DBColumn field : columnsFromQuery){
+
+				// Try to build/get appropriate metadata for this field/column:
+				TAPColumn colFromResult = (columnsFromResult != null && indField < columnsFromResult.length) ? columnsFromResult[indField] : null;
+				TAPColumn tapCol = getValidColMeta(field, colFromResult);
+
+				// Ensure these metadata are well returned at the end of this function:
+				columnsFromQuery[indField] = tapCol;
+
+				// Write the field/column metadata in the JSON output:
+				writeFieldMeta(tapCol, output);
+				indField++;
+
+				if (thread.isInterrupted())
+					throw new InterruptedException();
+			}
+		}else
+			output.println("<INFO name=\"WARNING\" value=\"MISSING_META\">Error while getting field(s) metadata</INFO>");
+
+		return columnsFromQuery;
+	}
+
+	/**
+	 * Try to get or otherwise to build appropriate metadata using those extracted from the ADQL query and those extracted from the result.
+	 * 
+	 * @param typeFromQuery		Metadata extracted/guessed from the ADQL query.
+	 * @param typeFromResult	Metadata extracted/guessed from the result.
+	 * 
+	 * @return	The most appropriate metadata.
+	 */
+	protected TAPColumn getValidColMeta(final DBColumn typeFromQuery, final TAPColumn typeFromResult){
+		if (typeFromQuery != null && typeFromQuery instanceof TAPColumn)
+			return (TAPColumn)typeFromQuery;
+		else if (typeFromResult != null){
+			if (typeFromQuery != null)
+				return (TAPColumn)typeFromResult.copy(typeFromQuery.getDBName(), typeFromQuery.getADQLName(), null);
+			else
+				return (TAPColumn)typeFromResult.copy();
+		}else
+			return new TAPColumn((typeFromQuery != null) ? typeFromQuery.getADQLName() : "?", new TAPType(TAPDatatype.VARCHAR), "?");
+	}
 
 	/**
 	 * <p>Formats in a VOTable field and writes the given {@link TAPColumn} in the given Writer.</p>
@@ -232,34 +275,49 @@ public abstract class VOTableFormat< R > implements OutputFormat<R> {
 	protected void writeFieldMeta(TAPColumn col, PrintWriter out) throws IOException, TAPException{
 		StringBuffer fieldline = new StringBuffer("\t\t\t");
 
+		// <FIELD ID="..."
 		fieldline.append("<FIELD ID=").append('"').append(SavotWriter.encodeAttribute(col.getADQLName())).append('"');
+
+		// name="..."
 		fieldline.append(" name=").append('"').append(SavotWriter.encodeAttribute(col.getADQLName())).append('"');
 
+		// datatype="..."
 		VotType type = col.getVotType();
-		String nullVal = getNullValue(type.datatype), description = null;
-
+		String nullVal = getNullValue(type.datatype);
 		fieldline.append(' ').append(type.toString());
 
+		// ucd="..." (if any)
 		if (col.getUcd() != null && col.getUcd().length() > 0)
 			fieldline.append(" ucd=").append('"').append(SavotWriter.encodeAttribute(col.getUcd())).append('"');
 
+		// utype="..." (if any)
 		if (col.getUtype() != null && col.getUtype().length() > 0)
 			fieldline.append(" utype=").append('"').append(SavotWriter.encodeAttribute(col.getUtype())).append('"');
 
+		// unit="..." (if any)
 		if (col.getUnit() != null && col.getUnit().length() > 0)
 			fieldline.append(" unit=").append('"').append(SavotWriter.encodeAttribute(col.getUnit())).append('"');
 
+		// Get the description (or NULL if none is provided):
+		String description = null;
 		if (col.getDescription() != null && !col.getDescription().trim().isEmpty())
 			description = col.getDescription().trim();
 		else
 			description = null;
 
+		// Declares NULL values and write the description:
 		if (nullVal != null || description != null){
 			fieldline.append(">\n");
+
+			// <VALUES null="..." /> (if needed)
 			if (nullVal != null)
 				fieldline.append("<VALUES null=\"" + nullVal + "\" />\n");
+
+			// <DESCRIPTION>...</DESCRIPTION> (if any)
 			if (description != null)
 				fieldline.append("<DESCRIPTION>").append(SavotWriter.encodeElement(description)).append("</DESCRIPTION>\n");
+
+			// </FIELD>
 			fieldline.append("</FIELD>");
 			out.println(fieldline);
 		}else{
@@ -269,22 +327,57 @@ public abstract class VOTableFormat< R > implements OutputFormat<R> {
 	}
 
 	/**
-	 * <p>Writes the data of the given query result in the given OutputStream.</p>
-	 * <p><b><u>Important:</u> To write a field value you can use {@link #writeFieldValue(Object, DBColumn, OutputStream)}.</b></p>
+	 * Write the whole data part (TABLEDATA) of the VOTable file.
 	 * 
-	 * @param queryResult		The query result which contains the data to write.
+	 * @param result			The query result which contains the data to write.
 	 * @param selectedColumns	The columns selected by the query.
 	 * @param output			The stream in which the data must be written.
-	 * @param execReport		The report of the query execution.
-	 * @param thread		The thread which asked for the result writting.
+	 * @param execReport		The report of the query execution (which contains the maximum allowed number of records to output).
+	 * @param thread			The thread which asked for the result writing.
 	 * 
-	 * @return					The number of written rows. (<i>note: if this number is greater than the value of MAXREC: OVERFLOW</i>)
+	 * @return	The number of written rows. (<i>note: if this number is greater than the value of MAXREC: OVERFLOW</i>)
 	 * 
 	 * @throws IOException				If there is an error while writing the data in the given stream.
-	 * @throws TAPException				If there is any other error.
 	 * @throws InterruptedException		If the given thread has been interrupted.
+	 * @throws TAPException				If there is any other error.
 	 */
-	protected abstract int writeData(final R queryResult, final DBColumn[] selectedColumns, final OutputStream output, final TAPExecutionReport execReport, final Thread thread) throws IOException, TAPException, InterruptedException;
+	protected int writeData(final TableIterator result, final DBColumn[] selectedColumns, final OutputStream output, final TAPExecutionReport execReport, final Thread thread) throws IOException, TAPException, InterruptedException{
+		// <TABLEDATA>
+		output.write("\t\t\t\t<TABLEDATA>\n".getBytes());
+
+		int nbRows = 0;
+		while(result.nextRow()){
+			// Deal with OVERFLOW, if needed:
+			if (execReport.parameters.getMaxRec() > 0 && nbRows >= execReport.parameters.getMaxRec())
+				break;
+
+			// <TR>
+			output.write("\t\t\t\t\t<TR>\n".getBytes());
+			int indCol = 0;
+			while(result.hasNextCol()){
+				// <TD>
+				output.write("\t\t\t\t\t\t<TD>".getBytes());
+				// ...
+				writeFieldValue(result.nextCol(), selectedColumns[indCol++], output);
+				// </TD>
+				output.write("</TD>\n".getBytes());
+
+				if (thread.isInterrupted())
+					throw new InterruptedException();
+			}
+
+			// </TR>
+			output.write("\t\t\t\t\t</TR>\n".getBytes());
+			nbRows++;
+
+			if (thread.isInterrupted())
+				throw new InterruptedException();
+		}
+
+		// </TABLEDATA>
+		output.write("\t\t\t\t</TABLEDATA>\n".getBytes());
+		return nbRows;
+	}
 
 	/**
 	 * <p>Writes the given field value in the given OutputStream.</p>
@@ -292,7 +385,8 @@ public abstract class VOTableFormat< R > implements OutputFormat<R> {
 	 * <p>
 	 * 	The given value will be encoded as an XML element (see {@link SavotWriter#encodeElement(String)}.
 	 * 	Besides, if the given value is <code>null</code> and if the column datatype is <code>int</code>,
-	 * 	<code>short</code> or <code>long</code>, the NULL values declared in the field metadata will be written.</p>
+	 * 	<code>short</code> or <code>long</code>, the NULL values declared in the field metadata will be written.
+	 * </p>
 	 * 
 	 * @param value				The value to write.
 	 * @param column			The corresponding column metadata.
@@ -322,19 +416,19 @@ public abstract class VOTableFormat< R > implements OutputFormat<R> {
 	 * 
 	 * @return			The corresponding NULL value, or <code>null</code> if there is none.
 	 */
-	public static final String getNullValue(String datatype){
+	public static final String getNullValue(VotDatatype datatype){
 		if (datatype == null)
 			return null;
 
-		datatype = datatype.trim().toLowerCase();
-
-		if (datatype.equals("short"))
-			return "" + Short.MIN_VALUE;
-		else if (datatype.equals("int"))
-			return "" + Integer.MIN_VALUE;
-		else if (datatype.equals("long"))
-			return "" + Long.MIN_VALUE;
-		else
-			return null;
+		switch(datatype){
+			case SHORT:
+				return "" + Short.MIN_VALUE;
+			case INT:
+				return "" + Integer.MIN_VALUE;
+			case LONG:
+				return "" + Long.MIN_VALUE;
+			default:
+				return null;
+		}
 	}
 }
diff --git a/src/tap/log/DefaultTAPLog.java b/src/tap/log/DefaultTAPLog.java
index 62b6971bcef008cfa401467d91059598b8cd2be1..10b22de68eb8a52e1bc17faf163a9c5f6a4bf767 100644
--- a/src/tap/log/DefaultTAPLog.java
+++ b/src/tap/log/DefaultTAPLog.java
@@ -16,7 +16,8 @@ package tap.log;
  * 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 2012 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomisches Rechen Institut (ARI)
  */
 
 import java.io.OutputStream;
@@ -24,19 +25,16 @@ import java.io.PrintWriter;
 
 import tap.TAPExecutionReport;
 import tap.db.DBConnection;
-
 import tap.file.TAPFileManager;
-
 import tap.metadata.TAPMetadata;
 import tap.metadata.TAPTable;
-
 import uws.service.log.DefaultUWSLog;
 
 /**
  * Default implementation of the {@link TAPLog} interface which lets logging any message about a TAP service.
  * 
- * @author Gr&eacute;gory Mantelet (CDS)
- * @version 06/2012
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 2.0 (07/2014)
  * 
  * @see DefaultUWSLog
  */
@@ -54,6 +52,7 @@ public class DefaultTAPLog extends DefaultUWSLog implements TAPLog {
 		super(writer);
 	}
 
+	@Override
 	public void queryFinished(final TAPExecutionReport report){
 		StringBuffer buffer = new StringBuffer("QUERY END FOR " + report.jobID + "");
 		buffer.append(" - success ? ").append(report.success);
@@ -61,7 +60,6 @@ public class DefaultTAPLog extends DefaultUWSLog implements TAPLog {
 		buffer.append(" - total duration = ").append(report.getTotalDuration()).append("ms");
 		buffer.append(" => upload=").append(report.getUploadDuration()).append("ms");
 		buffer.append(", parsing=").append(report.getParsingDuration()).append("ms");
-		buffer.append(", translating=").append(report.getTranslationDuration()).append("ms");
 		buffer.append(", execution=").append(report.getExecutionDuration()).append("ms");
 		buffer.append(", formatting[").append(report.parameters.getFormat()).append("]=").append(report.getFormattingDuration()).append("ms");
 		info(buffer.toString());
@@ -76,10 +74,12 @@ public class DefaultTAPLog extends DefaultUWSLog implements TAPLog {
 		log(DBConnection.LOG_TYPE_DB_ACTIVITY, ((message == null) ? null : (msgType + message)), t);
 	}
 
+	@Override
 	public void dbInfo(final String message){
 		dbActivity(message);
 	}
 
+	@Override
 	public void dbError(final String message, final Throwable t){
 		dbActivity(message, t);
 	}
@@ -95,71 +95,26 @@ public class DefaultTAPLog extends DefaultUWSLog implements TAPLog {
 	}
 
 	@Override
-	public void connectionOpened(DBConnection<?> connection, String dbName){
-		//dbActivity("A connection has been opened to the database \""+dbName+"\" !");
-	}
-
-	@Override
-	public void connectionClosed(DBConnection<?> connection){
+	public void connectionClosed(DBConnection connection){
 		//dbActivity("A database connection has been closed !");
 	}
 
-	@Override
-	public void transactionStarted(final DBConnection<?> connection){
-		//dbActivity("A transaction has been started !");
-	}
-
-	@Override
-	public void transactionCancelled(final DBConnection<?> connection){
-		//dbActivity("A transaction has been cancelled !");
-	}
-
-	@Override
-	public void transactionEnded(final DBConnection<?> connection){
-		//dbActivity("A transaction has been ended/commited !");
-	}
-
-	@Override
-	public void schemaCreated(final DBConnection<?> connection, String schema){
-		dbActivity("CREATE SCHEMA \"" + schema + "\"\t" + connection.getID());
-	}
-
-	@Override
-	public void schemaDropped(final DBConnection<?> connection, String schema){
-		dbActivity("DROP SCHEMA \"" + schema + "\"\t" + connection.getID());
-	}
-
 	protected final String getFullDBName(final TAPTable table){
 		return (table.getSchema() != null) ? (table.getSchema().getDBName() + ".") : "";
 	}
 
 	@Override
-	public void tableCreated(final DBConnection<?> connection, TAPTable table){
-		dbActivity("CREATE TABLE \"" + getFullDBName(table) + "\" (ADQL name: \"" + table.getFullName() + "\")\t" + connection.getID());
-	}
-
-	@Override
-	public void tableDropped(final DBConnection<?> connection, TAPTable table){
-		dbActivity("DROP TABLE \"" + getFullDBName(table) + "\" (ADQL name: \"" + table.getFullName() + "\")\t" + connection.getID());
-	}
-
-	@Override
-	public void rowsInserted(final DBConnection<?> connection, TAPTable table, int nbInsertedRows){
-		dbActivity("INSERT ROWS (" + ((nbInsertedRows > 0) ? nbInsertedRows : "???") + ") into \"" + getFullDBName(table) + "\" (ADQL name: \"" + table.getFullName() + "\")\t" + connection.getID());
-	}
-
-	@Override
-	public void sqlQueryExecuting(final DBConnection<?> connection, String sql){
+	public void sqlQueryExecuting(final DBConnection connection, String sql){
 		dbActivity("EXECUTING SQL QUERY \t" + connection.getID() + "\n" + ((sql == null) ? "???" : sql.replaceAll("\n", " ").replaceAll("\t", " ").replaceAll("\r", "")));
 	}
 
 	@Override
-	public void sqlQueryError(final DBConnection<?> connection, String sql, Throwable t){
+	public void sqlQueryError(final DBConnection connection, String sql, Throwable t){
 		dbActivity("EXECUTION ERROR\t" + connection.getID(), t);
 	}
 
 	@Override
-	public void sqlQueryExecuted(final DBConnection<?> connection, String sql){
+	public void sqlQueryExecuted(final DBConnection connection, String sql){
 		dbActivity("SUCCESSFULL END OF EXECUTION\t" + connection.getID());
 	}
 
diff --git a/src/tap/log/TAPLog.java b/src/tap/log/TAPLog.java
index 04439aa2297c4bdee7bb9f22e07f8aa8de2f5f86..6d15c8fd47adc69dde6fd29efde187c8ddc21f24 100644
--- a/src/tap/log/TAPLog.java
+++ b/src/tap/log/TAPLog.java
@@ -16,22 +16,20 @@ package tap.log;
  * 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 2012 - UDS/Centre de Données astronomiques de Strasbourg (CDS)
+ * Copyright 2012,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ *                       Astronomisches Rechen Institut (ARI)
  */
 
 import tap.TAPExecutionReport;
 import tap.db.DBConnection;
-
 import tap.metadata.TAPMetadata;
-import tap.metadata.TAPTable;
-
 import uws.service.log.UWSLog;
 
 /**
  * Lets logging any kind of message about a TAP service.
  * 
- * @author Gr&eacute;gory Mantelet (CDS)
- * @version 06/2012
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 2.0 (07/2014)
  */
 public interface TAPLog extends UWSLog {
 
@@ -45,30 +43,12 @@ public interface TAPLog extends UWSLog {
 
 	public void tapMetadataLoaded(final TAPMetadata metadata);
 
-	public void connectionOpened(final DBConnection<?> connection, final String dbName);
-
-	public void connectionClosed(final DBConnection<?> connection);
-
-	public void transactionStarted(final DBConnection<?> connection);
-
-	public void transactionCancelled(final DBConnection<?> connection);
-
-	public void transactionEnded(final DBConnection<?> connection);
-
-	public void schemaCreated(final DBConnection<?> connection, final String schema);
-
-	public void schemaDropped(final DBConnection<?> connection, final String schema);
-
-	public void tableCreated(final DBConnection<?> connection, final TAPTable table);
-
-	public void tableDropped(final DBConnection<?> connection, final TAPTable table);
-
-	public void rowsInserted(final DBConnection<?> connection, final TAPTable table, final int nbInsertedRows);
+	public void connectionClosed(final DBConnection connection);
 
-	public void sqlQueryExecuting(final DBConnection<?> connection, final String sql);
+	public void sqlQueryExecuting(final DBConnection connection, final String sql);
 
-	public void sqlQueryError(final DBConnection<?> connection, final String sql, final Throwable t);
+	public void sqlQueryError(final DBConnection connection, final String sql, final Throwable t);
 
-	public void sqlQueryExecuted(final DBConnection<?> connection, final String sql);
+	public void sqlQueryExecuted(final DBConnection connection, final String sql);
 
 }
diff --git a/src/tap/metadata/TAPDM.java b/src/tap/metadata/TAPDM.java
new file mode 100644
index 0000000000000000000000000000000000000000..5a0548face612fceedaf6ccd598cdcd42e74053f
--- /dev/null
+++ b/src/tap/metadata/TAPDM.java
@@ -0,0 +1,52 @@
+package tap.metadata;
+
+/*
+ * 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 Institute (ARI)
+ */
+
+/**
+ * Enumeration of all schemas and tables of the TAP datamodel (and particularly of TAP_SCHEMA).
+ * 
+ * @author Gr&eacute;gory Mantelet (ARI)
+ * @version 2.0 (07/2014)
+ * @since 2.0
+ */
+public enum TAPDM{
+	TAPSCHEMA("TAP_SCHEMA"), SCHEMAS("schemas"), TABLES("tables"), COLUMNS("columns"), FOREIGN_KEYS("foreign_keys"), UPLOADSCHEMA("TAP_UPLOAD");
+
+	/** Real name of the schema/table. */
+	private final String label;
+
+	private TAPDM(final String name){
+		this.label = name;
+	}
+
+	/**
+	 * Get the real name of the schema/table of the TAP datamodel.
+	 * 
+	 * @return	Real name of the schema/table.
+	 */
+	public String getLabel(){
+		return label;
+	}
+
+	@Override
+	public String toString(){
+		return label;
+	}
+}
diff --git a/src/tap/metadata/VotType.java b/src/tap/metadata/VotType.java
index c72db5c48b43a3a88c1e2290c3aa374104e7932d..000f5f81add035c5f1f6a8acce2c22c719726a30 100644
--- a/src/tap/metadata/VotType.java
+++ b/src/tap/metadata/VotType.java
@@ -31,18 +31,18 @@ import cds.savot.writer.SavotWriter;
  * </ul>
  * 
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 06/2014
+ * @version 2.0 (07/2014)
  */
 public final class VotType {
 	/**
 	 * All possible values for a VOTable datatype (i.e. boolean, short, char, ...).
 	 * 
 	 * @author Gr&eacute;gory Mantelet (ARI) - gmantele@ari.uni-heidelberg.de
-	 * @version 2.0 (06/2014)
+	 * @version 2.0 (07/2014)
 	 * @since 2.0
 	 */
 	public static enum VotDatatype{
-		BOOLEAN("boolean"), SHORT("short"), INT("int"), LONG("long"), FLOAT("float"), DOUBLE("double"), CHAR("char"), UNSIGNED_BYTE("unsignedByte");
+		BOOLEAN("boolean"), BIT("bit"), UNSIGNED_BYTE("unsignedByte"), SHORT("short"), INT("int"), LONG("long"), CHAR("char"), UNICODE_CHAR("unicodeChar"), FLOAT("float"), DOUBLE("double"), FLOAT_COMPLEX("floatComplex"), DOUBLE_COMPLEX("doubleComplex");
 
 		private final String strExpr;
 
diff --git a/src/tap/parameters/FormatController.java b/src/tap/parameters/FormatController.java
index 46f2d2fea72773f756ff2bee96075036eccb7060..526536fece92570323fcc60e959e2450d8120a16 100644
--- a/src/tap/parameters/FormatController.java
+++ b/src/tap/parameters/FormatController.java
@@ -28,12 +28,12 @@ import uws.UWSException;
 import uws.UWSExceptionFactory;
 import uws.job.parameters.InputParamController;
 
-public class FormatController< R > implements InputParamController {
+public class FormatController implements InputParamController {
 
-	protected final ServiceConnection<R> service;
+	protected final ServiceConnection service;
 	protected boolean allowModification = true;
 
-	public FormatController(final ServiceConnection<R> service){
+	public FormatController(final ServiceConnection service){
 		this.service = service;
 	}
 
@@ -70,10 +70,10 @@ public class FormatController< R > implements InputParamController {
 	}
 
 	public final String getAllowedFormats(){
-		Iterator<OutputFormat<R>> itFormats = service.getOutputFormats();
+		Iterator<OutputFormat> itFormats = service.getOutputFormats();
 		StringBuffer allowedFormats = new StringBuffer();
 		int i = 0;
-		OutputFormat<R> formatter;
+		OutputFormat formatter;
 		while(itFormats.hasNext()){
 			formatter = itFormats.next();
 			allowedFormats.append((i == 0) ? "" : ", ").append(formatter.getMimeType());
diff --git a/src/tap/parameters/MaxRecController.java b/src/tap/parameters/MaxRecController.java
index 08f29b6c605ccce2f2dafb2e3636bda05b630261..30851bc4d51456f1661b0f9a39a1512b362f8606 100644
--- a/src/tap/parameters/MaxRecController.java
+++ b/src/tap/parameters/MaxRecController.java
@@ -39,12 +39,12 @@ import uws.job.parameters.InputParamController;
  */
 public class MaxRecController implements InputParamController {
 
-	protected final ServiceConnection<?> service;
+	protected final ServiceConnection service;
 
 	/** Indicates whether the output limit of jobs can be modified. */
 	protected boolean allowModification = true;
 
-	public MaxRecController(final ServiceConnection<?> service){
+	public MaxRecController(final ServiceConnection service){
 		this.service = service;
 		allowModification(allowModification);
 	}
diff --git a/src/tap/parameters/TAPDestructionTimeController.java b/src/tap/parameters/TAPDestructionTimeController.java
index 33444bdcf35ecae0b0515686f98552e69478f774..a3c0ca644fdd73c622c704525b9779adc12cabbd 100644
--- a/src/tap/parameters/TAPDestructionTimeController.java
+++ b/src/tap/parameters/TAPDestructionTimeController.java
@@ -25,20 +25,18 @@ import java.util.Date;
 
 import tap.ServiceConnection;
 import tap.TAPJob;
-
 import uws.UWSException;
 import uws.UWSExceptionFactory;
-
 import uws.job.UWSJob;
-import uws.job.parameters.InputParamController;
 import uws.job.parameters.DestructionTimeController.DateField;
+import uws.job.parameters.InputParamController;
 
 public class TAPDestructionTimeController implements InputParamController {
 
-	protected final ServiceConnection<?> service;
+	protected final ServiceConnection service;
 	protected boolean allowModification = true;
 
-	public TAPDestructionTimeController(final ServiceConnection<?> service){
+	public TAPDestructionTimeController(final ServiceConnection service){
 		this.service = service;
 	}
 
diff --git a/src/tap/parameters/TAPExecutionDurationController.java b/src/tap/parameters/TAPExecutionDurationController.java
index 68f5797e6bb8860f3917d749ce4083da49fb0388..2187beda9b0a7a9fba3a13413d86a378a9d68b28 100644
--- a/src/tap/parameters/TAPExecutionDurationController.java
+++ b/src/tap/parameters/TAPExecutionDurationController.java
@@ -21,18 +21,16 @@ package tap.parameters;
 
 import tap.ServiceConnection;
 import tap.TAPJob;
-
 import uws.UWSException;
 import uws.UWSExceptionFactory;
-
 import uws.job.parameters.InputParamController;
 
 public class TAPExecutionDurationController implements InputParamController {
 
-	protected final ServiceConnection<?> service;
+	protected final ServiceConnection service;
 	protected boolean allowModification = true;
 
-	public TAPExecutionDurationController(final ServiceConnection<?> service){
+	public TAPExecutionDurationController(final ServiceConnection service){
 		this.service = service;
 	}
 
diff --git a/src/tap/parameters/TAPParameters.java b/src/tap/parameters/TAPParameters.java
index f45c833ff840e87d28ac3c7a5e9a9a93f16a473b..697e37a3d7ae11687fbca0631bb76ab3e1a43ec0 100644
--- a/src/tap/parameters/TAPParameters.java
+++ b/src/tap/parameters/TAPParameters.java
@@ -21,7 +21,6 @@ package tap.parameters;
 
 import java.io.File;
 import java.io.IOException;
-
 import java.util.Collection;
 import java.util.Date;
 import java.util.Enumeration;
@@ -30,21 +29,18 @@ import java.util.Map;
 
 import javax.servlet.http.HttpServletRequest;
 
-import com.oreilly.servlet.MultipartRequest;
-import com.oreilly.servlet.multipart.FileRenamePolicy;
-
 import tap.ServiceConnection;
 import tap.TAPException;
 import tap.TAPJob;
-
 import tap.upload.TableLoader;
-
 import uws.UWSException;
-
 import uws.job.parameters.InputParamController;
 import uws.job.parameters.StringParamController;
 import uws.job.parameters.UWSParameters;
 
+import com.oreilly.servlet.MultipartRequest;
+import com.oreilly.servlet.multipart.FileRenamePolicy;
+
 /**
  * This class describes all defined parameters of a TAP request.
  * 
@@ -65,21 +61,21 @@ public class TAPParameters extends UWSParameters {
 	protected TableLoader[] tablesToUpload = null;
 
 	@SuppressWarnings({"unchecked"})
-	public TAPParameters(final ServiceConnection<?> service){
+	public TAPParameters(final ServiceConnection service){
 		this(service, (Collection)null, null);
 	}
 
-	public TAPParameters(final ServiceConnection<?> service, final Collection<String> expectedAdditionalParams, final Map<String,InputParamController> inputParamControllers){
+	public TAPParameters(final ServiceConnection service, final Collection<String> expectedAdditionalParams, final Map<String,InputParamController> inputParamControllers){
 		super(expectedAdditionalParams, inputParamControllers);
 		initDefaultTAPControllers(service);
 	}
 
-	public TAPParameters(final HttpServletRequest request, final ServiceConnection<?> service) throws UWSException, TAPException{
+	public TAPParameters(final HttpServletRequest request, final ServiceConnection service) throws UWSException, TAPException{
 		this(request, service, null, null);
 	}
 
 	@SuppressWarnings("unchecked")
-	public TAPParameters(final HttpServletRequest request, final ServiceConnection<?> service, final Collection<String> expectedAdditionalParams, final Map<String,InputParamController> inputParamControllers) throws UWSException, TAPException{
+	public TAPParameters(final HttpServletRequest request, final ServiceConnection service, final Collection<String> expectedAdditionalParams, final Map<String,InputParamController> inputParamControllers) throws UWSException, TAPException{
 		this(service, expectedAdditionalParams, inputParamControllers);
 		MultipartRequest multipart = null;
 
@@ -128,11 +124,11 @@ public class TAPParameters extends UWSParameters {
 			tablesToUpload = buildLoaders(uploadParam, multipart);
 	}
 
-	public TAPParameters(final ServiceConnection<?> service, final Map<String,Object> params) throws UWSException, TAPException{
+	public TAPParameters(final ServiceConnection service, final Map<String,Object> params) throws UWSException, TAPException{
 		this(service, params, null, null);
 	}
 
-	public TAPParameters(final ServiceConnection<?> service, final Map<String,Object> params, final Collection<String> expectedAdditionalParams, final Map<String,InputParamController> inputParamControllers) throws UWSException, TAPException{
+	public TAPParameters(final ServiceConnection service, final Map<String,Object> params, final Collection<String> expectedAdditionalParams, final Map<String,InputParamController> inputParamControllers) throws UWSException, TAPException{
 		super(params, expectedAdditionalParams, inputParamControllers);
 		initDefaultTAPControllers(service);
 	}
@@ -142,7 +138,7 @@ public class TAPParameters extends UWSParameters {
 		return new HashMap<String,InputParamController>(10);
 	}
 
-	protected < R > void initDefaultTAPControllers(final ServiceConnection<R> service){
+	protected < R > void initDefaultTAPControllers(final ServiceConnection service){
 		if (!mapParamControllers.containsKey(TAPJob.PARAM_EXECUTION_DURATION))
 			mapParamControllers.put(TAPJob.PARAM_EXECUTION_DURATION, new TAPExecutionDurationController(service));
 
@@ -165,7 +161,7 @@ public class TAPParameters extends UWSParameters {
 			mapParamControllers.put(TAPJob.PARAM_UPLOAD, new StringParamController(TAPJob.PARAM_UPLOAD));
 
 		if (!mapParamControllers.containsKey(TAPJob.PARAM_FORMAT))
-			mapParamControllers.put(TAPJob.PARAM_FORMAT, new FormatController<R>(service));
+			mapParamControllers.put(TAPJob.PARAM_FORMAT, new FormatController(service));
 
 		if (!mapParamControllers.containsKey(TAPJob.PARAM_MAX_REC))
 			mapParamControllers.put(TAPJob.PARAM_MAX_REC, new MaxRecController(service));
diff --git a/src/tap/resource/Availability.java b/src/tap/resource/Availability.java
index 73832f8c6fb939ba0770769cfd2406a45fd3dcd1..1da467efa05d65b20ffc94cac44e33a7181d1f42 100644
--- a/src/tap/resource/Availability.java
+++ b/src/tap/resource/Availability.java
@@ -33,17 +33,18 @@ public class Availability implements TAPResource, VOSIResource {
 
 	public static final String RESOURCE_NAME = "availability";
 
-	private final ServiceConnection<?> service;
+	private final ServiceConnection service;
 	protected String accessURL = getName();
 
-	protected Availability(ServiceConnection<?> service){
+	protected Availability(ServiceConnection service){
 		this.service = service;
 	}
 
-	public ServiceConnection<?> getService(){
+	public ServiceConnection getService(){
 		return service;
 	}
 
+	@Override
 	public final void setTAPBaseURL(String baseURL){
 		accessURL = ((baseURL == null) ? "" : (baseURL + "/")) + getName();
 	}
diff --git a/src/tap/resource/Capabilities.java b/src/tap/resource/Capabilities.java
index be6ff926f639ac43b5859f3aa9f94ff026f345f3..4cbdba0d562cbe9f7b7dc282c2edf1dd0967c4bf 100644
--- a/src/tap/resource/Capabilities.java
+++ b/src/tap/resource/Capabilities.java
@@ -32,15 +32,16 @@ public class Capabilities implements TAPResource, VOSIResource {
 
 	public static final String RESOURCE_NAME = "capabilities";
 
-	private final TAP<?> tap;
+	private final TAP tap;
 	protected String accessURL = getName();
 
-	public Capabilities(TAP<?> tap){
+	public Capabilities(TAP tap){
 		this.tap = tap;
 	}
 
 	/**
 	 */
+	@Override
 	public final void setTAPBaseURL(String baseURL){
 		accessURL = ((baseURL == null) ? "" : (baseURL + "/")) + getName();
 	}
diff --git a/src/tap/resource/Sync.java b/src/tap/resource/Sync.java
index 71be76f48da2c1312fe70a6497fd646a06603d25..43825dc1be79ef22dd6116cc7e91b22c729dc621 100644
--- a/src/tap/resource/Sync.java
+++ b/src/tap/resource/Sync.java
@@ -20,14 +20,15 @@ package tap.resource;
  */
 
 import java.io.IOException;
+
 import javax.servlet.ServletConfig;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
-import tap.TAPJob;
 import tap.ServiceConnection;
 import tap.TAPException;
+import tap.TAPJob;
 import tap.TAPSyncJob;
 import tap.parameters.TAPParameters;
 import uws.UWSException;
@@ -38,11 +39,11 @@ public class Sync implements TAPResource {
 
 	protected String accessURL = null;
 
-	protected final ServiceConnection<?> service;
+	protected final ServiceConnection service;
 
 	protected final Capabilities capabilities;
 
-	public Sync(ServiceConnection<?> service, Capabilities capabilities){
+	public Sync(ServiceConnection service, Capabilities capabilities){
 		this.service = service;
 		this.capabilities = capabilities;
 	}
diff --git a/src/tap/resource/TAP.java b/src/tap/resource/TAP.java
index 3ad3a555d13bc52bf1060a4839bab34d5ce32998..db12b135546bdb6d21b5a649d805bb4e62a16127 100644
--- a/src/tap/resource/TAP.java
+++ b/src/tap/resource/TAP.java
@@ -55,11 +55,11 @@ import uws.service.UWSService;
 import uws.service.UWSUrl;
 import uws.service.error.ServiceErrorWriter;
 
-public class TAP< R > implements VOSIResource {
+public class TAP implements VOSIResource {
 
 	private static final long serialVersionUID = 1L;
 
-	protected final ServiceConnection<R> service;
+	protected final ServiceConnection service;
 
 	protected final Map<String,TAPResource> resources;
 
@@ -69,7 +69,7 @@ public class TAP< R > implements VOSIResource {
 
 	protected ServiceErrorWriter errorWriter;
 
-	public TAP(ServiceConnection<R> serviceConnection) throws UWSException, TAPException{
+	public TAP(ServiceConnection serviceConnection) throws UWSException, TAPException{
 		service = serviceConnection;
 		resources = new HashMap<String,TAPResource>();
 
@@ -89,11 +89,12 @@ public class TAP< R > implements VOSIResource {
 		getUWS().setErrorWriter(errorWriter);
 
 		if (service.uploadEnabled()){
-			DBConnection<?> dbConn = null;
+			DBConnection dbConn = null;
 			try{
 				dbConn = service.getFactory().createDBConnection("TAP(ServiceConnection)");
-				dbConn.dropSchema("TAP_UPLOAD");
-				dbConn.createSchema("TAP_UPLOAD");
+				// TODO CLEAN ACTION: DROP SCHEMA!
+				/*dbConn.dropSchema("TAP_UPLOAD");
+				dbConn.createSchema("TAP_UPLOAD");*/
 			}catch(TAPException e){
 				throw new UWSException(UWSException.INTERNAL_SERVER_ERROR, e, "Error while creating the schema TAP_UPLOAD !");
 			}finally{
@@ -178,8 +179,8 @@ public class TAP< R > implements VOSIResource {
 		xml.append("\t\t<description>ADQL 2.0</description>\n");
 		xml.append("\t</language>\n");
 
-		Iterator<OutputFormat<R>> itFormats = service.getOutputFormats();
-		OutputFormat<R> formatter;
+		Iterator<OutputFormat> itFormats = service.getOutputFormats();
+		OutputFormat formatter;
 		while(itFormats.hasNext()){
 			formatter = itFormats.next();
 			xml.append("\t<outputFormat>\n");
diff --git a/src/tap/upload/Uploader.java b/src/tap/upload/Uploader.java
index 651a198c2b8a5b8c518766a47eb3fdccda73a809..76bbab4842d705a9ab6417f48bf62f0198f79117 100644
--- a/src/tap/upload/Uploader.java
+++ b/src/tap/upload/Uploader.java
@@ -26,113 +26,126 @@ import java.io.InputStream;
 import tap.ServiceConnection;
 import tap.ServiceConnection.LimitUnit;
 import tap.TAPException;
+import tap.data.DataReadException;
+import tap.data.VOTableIterator;
 import tap.db.DBConnection;
-import tap.db.DBException;
+import tap.metadata.TAPColumn;
+import tap.metadata.TAPDM;
 import tap.metadata.TAPSchema;
 import tap.metadata.TAPTable;
-import tap.metadata.TAPTypes;
-import tap.metadata.VotType;
-import cds.savot.model.DataBinaryReader;
-import cds.savot.model.FieldSet;
-import cds.savot.model.SavotBinary;
-import cds.savot.model.SavotField;
-import cds.savot.model.SavotResource;
-import cds.savot.model.SavotTR;
-import cds.savot.model.SavotTableData;
-import cds.savot.model.TRSet;
-import cds.savot.pull.SavotPullEngine;
-import cds.savot.pull.SavotPullParser;
 
 import com.oreilly.servlet.multipart.ExceededSizeException;
 
 /**
+ * <p>Let upload properly given VOTable inputs.</p>
  * 
- * @author Gr&eacute;gory Mantelet (CDS;ARI) - gmantele@ari.uni-heidelberg.de
- * @version 1.1 (03/2014)
+ * <p>This class manages particularly the upload limit in rows
+ * (thanks to {@link VOTableIterator}) and in bytes (thanks to a {@link LimitedSizeInputStream}).</p>
+ * 
+ * 
+ * @author Gr&eacute;gory Mantelet (CDS;ARI)
+ * @version 2.0 (07/2014)
  */
 public class Uploader {
 
-	protected final ServiceConnection<?> service;
-	protected final DBConnection<?> dbConn;
+	/** Specification of the TAP service. */
+	protected final ServiceConnection service;
+	/** Connection to the "database" (which lets upload the content of any given VOTable). */
+	protected final DBConnection dbConn;
+	/** Limit on the number of rows allowed to be uploaded in once (whatever is the number of tables). */
 	protected final int nbRowsLimit;
+	/** Limit on the number of bytes allowed to be uploaded in once (whatever is the number of tables). */
 	protected final int nbBytesLimit;
 
+	/** Number of rows already loaded. */
 	protected int nbRows = 0;
 
-	public Uploader(final ServiceConnection<?> service, final DBConnection<?> dbConn) throws TAPException{
+	/**
+	 * Build an {@link Uploader} object.
+	 * 
+	 * @param service	Specification of the TAP service using this uploader.
+	 * @param dbConn	A valid (open) connection to the "database".
+	 * 
+	 * @throws TAPException	If any error occurs while building this {@link Uploader}.
+	 */
+	public Uploader(final ServiceConnection service, final DBConnection dbConn) throws TAPException{
+		// NULL tests:
 		if (service == null)
 			throw new NullPointerException("The given ServiceConnection is NULL !");
 		if (dbConn == null)
 			throw new NullPointerException("The given DBConnection is NULL !");
 
+		// Set the service and database connections:
 		this.service = service;
-
 		this.dbConn = dbConn;
 
-		if (service.uploadEnabled()){
-			if (service.getUploadLimitType()[1] == LimitUnit.rows){
-				nbRowsLimit = ((service.getUploadLimit()[1] > 0) ? service.getUploadLimit()[1] : -1);
+		// Ensure UPLOAD is allowed by the TAP service specification...
+		if (this.service.uploadEnabled()){
+			// ...and set the rows or bytes limit:
+			if (this.service.getUploadLimitType()[1] == LimitUnit.rows){
+				nbRowsLimit = ((this.service.getUploadLimit()[1] > 0) ? this.service.getUploadLimit()[1] : -1);
 				nbBytesLimit = -1;
 			}else{
-				nbBytesLimit = ((service.getUploadLimit()[1] > 0) ? service.getUploadLimit()[1] : -1);
+				nbBytesLimit = ((this.service.getUploadLimit()[1] > 0) ? this.service.getUploadLimit()[1] : -1);
 				nbRowsLimit = -1;
 			}
 		}else
 			throw new TAPException("Upload aborted: this functionality is disabled in this TAP service!");
 	}
 
+	/**
+	 * Upload all the given VOTable inputs.
+	 * 
+	 * @param loaders	Array of tables to upload.
+	 * 
+	 * @return	A {@link TAPSchema} containing the list and the description of all uploaded tables.
+	 * 
+	 * @throws TAPException	If any error occurs while reading the VOTable inputs or while uploading the table into the "database".
+	 * 
+	 * @see DBConnection#addUploadedTable(TAPTable, tap.data.TableIterator)
+	 */
 	public TAPSchema upload(final TableLoader[] loaders) throws TAPException{
-		// Begin a DB transaction:
-		dbConn.startTransaction();
-
-		TAPSchema uploadSchema = new TAPSchema("TAP_UPLOAD");
+		TAPSchema uploadSchema = new TAPSchema(TAPDM.UPLOADSCHEMA.getLabel());
 		InputStream votable = null;
 		String tableName = null;
-		nbRows = 0;
 		try{
 			for(TableLoader loader : loaders){
 				tableName = loader.tableName;
+
+				// Open a stream toward the VOTable:
 				votable = loader.openStream();
 
+				// Set a byte limit if one is required:
 				if (nbBytesLimit > 0)
 					votable = new LimitedSizeInputStream(votable, nbBytesLimit);
 
-				// start parsing the VOTable:
-				SavotPullParser parser = new SavotPullParser(votable, SavotPullEngine.SEQUENTIAL, null);
+				// Start reading the VOTable:
+				VOTableIterator dataIt = new VOTableIterator(votable);
 
-				SavotResource resource = parser.getNextResource();
-				if (resource == null)
-					throw new TAPException("Incorrect VOTable format !");
+				// Define the table to upload:
+				TAPColumn[] columns = dataIt.getMetadata();
+				TAPTable table = new TAPTable(tableName);
+				table.setDBName(tableName + "_" + System.currentTimeMillis());
+				for(TAPColumn col : columns)
+					table.addColumn(col);
 
-				FieldSet fields = resource.getFieldSet(0);
+				// Add the table to the TAP_UPLOAD schema:
+				uploadSchema.addTable(table);
 
-				// 1st STEP: Convert the VOTable metadata into DBTable:
-				TAPTable tapTable = fetchTableMeta(tableName, System.currentTimeMillis() + "", fields);
-				uploadSchema.addTable(tapTable);
-
-				// 2nd STEP: Create the corresponding table in the database:
-				dbConn.createTable(tapTable);
-
-				// 3rd STEP: Load rows into this table:
-				SavotBinary binary = resource.getData(0).getBinary();
-				if (binary != null)
-					loadTable(tapTable, fields, binary);
-				else
-					loadTable(tapTable, fields, resource.getData(0).getTableData());
+				// Create and fill the corresponding table in the database:
+				dbConn.addUploadedTable(table, dataIt, nbRowsLimit);
 
+				// Close the VOTable stream:
 				votable.close();
 			}
-		}catch(DBException dbe){
-			dbConn.cancelTransaction();	// ROLLBACK
-			throw dbe;
-		}catch(ExceededSizeException ese){
-			dbConn.cancelTransaction();	// ROLLBACK
-			throw new TAPException("Upload limit exceeded ! You can upload at most " + ((nbBytesLimit > 0) ? (nbBytesLimit + " bytes.") : (nbRowsLimit + " rows.")));
+		}catch(DataReadException dre){
+			if (dre.getCause() instanceof ExceededSizeException)
+				throw new TAPException("Upload limit exceeded ! You can upload at most " + ((nbBytesLimit > 0) ? (nbBytesLimit + " bytes.") : (nbRowsLimit + " rows.")));
+			else
+				throw new TAPException("Error while reading the VOTable \"" + tableName + "\": " + dre.getMessage(), dre);
 		}catch(IOException ioe){
-			dbConn.cancelTransaction(); // ROLLBACK
-			throw new TAPException("Error while reading the VOTable of \"" + tableName + "\" !", ioe);
+			throw new TAPException("Error while reading the VOTable of \"" + tableName + "\"!", ioe);
 		}catch(NullPointerException npe){
-			dbConn.cancelTransaction();	// ROLLBACK
 			if (votable != null && votable instanceof LimitedSizeInputStream)
 				throw new TAPException("Upload limit exceeded ! You can upload at most " + ((nbBytesLimit > 0) ? (nbBytesLimit + " bytes.") : (nbRowsLimit + " rows.")));
 			else
@@ -146,77 +159,8 @@ public class Uploader {
 			}
 		}
 
-		// Commit modifications:
-		try{
-			dbConn.endTransaction();
-		}finally{
-			dbConn.close();
-		}
-
+		// Return the TAP_UPLOAD schema (containing just the uploaded tables):
 		return uploadSchema;
 	}
 
-	private TAPTable fetchTableMeta(final String tableName, final String userId, final FieldSet fields){
-		TAPTable tapTable = new TAPTable(tableName);
-		tapTable.setDBName(tableName + "_" + userId);
-
-		for(int j = 0; j < fields.getItemCount(); j++){
-			SavotField field = (SavotField)fields.getItemAt(j);
-			int arraysize = TAPTypes.NO_SIZE;
-			if (field.getArraySize() == null || field.getArraySize().trim().isEmpty())
-				arraysize = 1;
-			else if (field.getArraySize().equalsIgnoreCase("*"))
-				arraysize = TAPTypes.STAR_SIZE;
-			else{
-				try{
-					arraysize = Integer.parseInt(field.getArraySize());
-				}catch(NumberFormatException nfe){
-					service.getLogger().warning("Invalid array-size in the uploaded table \"" + tableName + "\" for the field \"" + field.getName() + "\": \"" + field.getArraySize() + "\" ! It will be considered as \"*\" !");
-				}
-			}
-			tapTable.addColumn(field.getName(), field.getDescription(), field.getUnit(), field.getUcd(), field.getUtype(), new VotType(field.getDataType(), arraysize, field.getXtype()), false, false, false);
-		}
-
-		return tapTable;
-	}
-
-	private int loadTable(final TAPTable tapTable, final FieldSet fields, final SavotBinary binary) throws TAPException, ExceededSizeException{
-		// Read the raw binary data:
-		DataBinaryReader reader = null;
-		try{
-			reader = new DataBinaryReader(binary.getStream(), fields, false);
-			while(reader.next()){
-				if (nbRowsLimit > 0 && nbRows >= nbRowsLimit)
-					throw new ExceededSizeException();
-				dbConn.insertRow(reader.getTR(), tapTable);
-				nbRows++;
-			}
-		}catch(ExceededSizeException ese){
-			throw ese;
-		}catch(IOException se){
-			throw new TAPException("Error while reading the binary data of the VOTable of \"" + tapTable.getADQLName() + "\" !", se);
-		}finally{
-			try{
-				if (reader != null)
-					reader.close();
-			}catch(IOException ioe){
-				;
-			}
-		}
-
-		return nbRows;
-	}
-
-	private int loadTable(final TAPTable tapTable, final FieldSet fields, final SavotTableData data) throws TAPException, ExceededSizeException{
-		TRSet rows = data.getTRs();
-		for(int i = 0; i < rows.getItemCount(); i++){
-			if (nbRowsLimit > 0 && nbRows >= nbRowsLimit)
-				throw new ExceededSizeException();
-			dbConn.insertRow((SavotTR)rows.getItemAt(i), tapTable);
-			nbRows++;
-		}
-
-		return nbRows;
-	}
-
 }
diff --git a/test/adql/SearchColumnListTest.java b/test/adql/SearchColumnListTest.java
index 776a3435b09af5b21a2a71623e5594e28db1fbe6..0a3b48dd9cc72e015313e50ba29cf54a7f468139 100644
--- a/test/adql/SearchColumnListTest.java
+++ b/test/adql/SearchColumnListTest.java
@@ -5,6 +5,11 @@ import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
 
+import tap.metadata.TAPColumn;
+import tap.metadata.TAPSchema;
+import tap.metadata.TAPTable;
+import tap.metadata.TAPType;
+import tap.metadata.TAPType.TAPDatatype;
 import adql.db.DBColumn;
 import adql.db.DBCommonColumn;
 import adql.db.DBTable;
@@ -13,9 +18,6 @@ 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 {
 
@@ -29,16 +31,16 @@ public class SearchColumnListTest {
 		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"));
+		tableA.addColumn(new TAPColumn("id", new TAPType(TAPDatatype.VARCHAR), "Object ID"));
+		tableA.addColumn(new TAPColumn("txta", new TAPType(TAPDatatype.VARCHAR), "Text of table A"));
+		tableB.addColumn(new TAPColumn("id", new TAPType(TAPDatatype.VARCHAR), "Object ID"));
+		tableB.addColumn(new TAPColumn("txtb", new TAPType(TAPDatatype.VARCHAR), "Text of table B"));
+		tableC.addColumn(new TAPColumn("Id", new TAPType(TAPDatatype.VARCHAR), "Object ID"));
+		tableC.addColumn(new TAPColumn("txta", new TAPType(TAPDatatype.VARCHAR), "Text of table A"));
+		tableC.addColumn(new TAPColumn("txtc", new TAPType(TAPDatatype.VARCHAR), "Text of table C"));
+		tableD.addColumn(new TAPColumn("id", new TAPType(TAPDatatype.VARCHAR), "Object ID"));
+		tableD.addColumn(new TAPColumn("txta", new TAPType(TAPDatatype.VARCHAR), "Text of table A"));
+		tableD.addColumn(new TAPColumn("txtd", new TAPType(TAPDatatype.VARCHAR), "Text of table D"));
 
 		// List all available tables:
 		TAPSchema schema = new TAPSchema("public");
diff --git a/test/tap/formatter/JSONFormatTest.java b/test/tap/formatter/JSONFormatTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..a037575c54bafb49cb09a88c44d954fbc86e5495
--- /dev/null
+++ b/test/tap/formatter/JSONFormatTest.java
@@ -0,0 +1,247 @@
+package tap.formatter;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.OutputStream;
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+
+import org.json.JSONObject;
+import org.json.JSONTokener;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import tap.ServiceConnection;
+import tap.TAPExecutionReport;
+import tap.TAPFactory;
+import tap.TAPJob;
+import tap.data.ResultSetTableIterator;
+import tap.data.TableIterator;
+import tap.file.TAPFileManager;
+import tap.log.TAPLog;
+import tap.metadata.TAPColumn;
+import tap.metadata.TAPMetadata;
+import tap.metadata.TAPType;
+import tap.metadata.TAPType.TAPDatatype;
+import tap.parameters.TAPParameters;
+import testtools.DBTools;
+import uws.service.UserIdentifier;
+
+/**
+ * <p>Test the JSONFormat function {@link JSONFormat#writeResult(TableIterator, OutputStream, TAPExecutionReport, Thread)}.</p>
+ * 
+ * <p>2 test ares done: 1 with an overflow and another without.</p>
+ * 
+ * @author Gr&eacute;gory Mantelet (ARI)
+ * @version 2.0 (07/2014)
+ */
+public class JSONFormatTest {
+
+	private static Connection conn;
+	private static ServiceConnection serviceConn;
+	private static TAPColumn[] resultingColumns;
+	private static File jsonFile = new File("/home/gmantele/Desktop/json_test.json");
+
+	@BeforeClass
+	public static void setUpBeforeClass() throws Exception{
+		conn = DBTools.createConnection("postgresql", "127.0.0.1", null, "gmantele", "gmantele", "pwd");
+		serviceConn = new ServiceConnectionTest();
+
+		resultingColumns = new TAPColumn[4];
+		resultingColumns[0] = new TAPColumn("ID", new TAPType(TAPDatatype.VARCHAR));
+		resultingColumns[1] = new TAPColumn("ra", new TAPType(TAPDatatype.DOUBLE), "Right ascension", "deg", "pos.eq.ra", null);
+		resultingColumns[2] = new TAPColumn("deg", new TAPType(TAPDatatype.DOUBLE), "Declination", "deg", "pos.eq.dec", null);
+		resultingColumns[3] = new TAPColumn("gmag", new TAPType(TAPDatatype.DOUBLE), "G magnitude", "mag", "phot.mag;em.opt.B", null);
+
+		if (!jsonFile.exists())
+			jsonFile.createNewFile();
+	}
+
+	@AfterClass
+	public static void tearDownAfterClass() throws Exception{
+		DBTools.closeConnection(conn);
+		jsonFile.delete();
+	}
+
+	@Test
+	public void testWriteResult(){
+		try{
+			ResultSet rs = DBTools.select(conn, "SELECT id, ra, deg, gmag FROM gums LIMIT 10;");
+
+			HashMap<String,Object> tapParams = new HashMap<String,Object>(1);
+			tapParams.put(TAPJob.PARAM_MAX_REC, "100");
+			TAPParameters params = new TAPParameters(serviceConn, tapParams);
+			TAPExecutionReport report = new TAPExecutionReport("123456A", true, params);
+			report.resultingColumns = resultingColumns;
+
+			TableIterator it = new ResultSetTableIterator(rs);
+
+			JSONFormat formatter = new JSONFormat(serviceConn);
+			OutputStream output = new BufferedOutputStream(new FileOutputStream(jsonFile));
+			formatter.writeResult(it, output, report, Thread.currentThread());
+			output.close();
+
+			JSONTokener tok = new JSONTokener(new FileInputStream(jsonFile));
+			JSONObject obj = (JSONObject)tok.nextValue();
+			assertEquals(obj.getJSONArray("data").length(), 10);
+
+		}catch(Exception t){
+			t.printStackTrace();
+			fail("Unexpected exception!");
+		}
+	}
+
+	@Test
+	public void testWriteResultWithOverflow(){
+		ResultSet rs = null;
+		try{
+			rs = DBTools.select(conn, "SELECT id, ra, deg, gmag FROM gums LIMIT 10;");
+
+			HashMap<String,Object> tapParams = new HashMap<String,Object>(1);
+			tapParams.put(TAPJob.PARAM_MAX_REC, "5");
+			TAPParameters params = new TAPParameters(serviceConn, tapParams);
+			TAPExecutionReport report = new TAPExecutionReport("123456A", true, params);
+			report.resultingColumns = resultingColumns;
+
+			TableIterator it = new ResultSetTableIterator(rs);
+
+			JSONFormat formatter = new JSONFormat(serviceConn);
+			OutputStream output = new BufferedOutputStream(new FileOutputStream(jsonFile));
+			formatter.writeResult(it, output, report, Thread.currentThread());
+			output.close();
+
+			JSONTokener tok = new JSONTokener(new FileInputStream(jsonFile));
+			JSONObject obj = (JSONObject)tok.nextValue();
+			assertEquals(obj.getJSONArray("data").length(), 5);
+
+		}catch(Exception t){
+			t.printStackTrace();
+			fail("Unexpected exception!");
+		}finally{
+			if (rs != null){
+				try{
+					rs.close();
+				}catch(SQLException e){
+					System.err.println("Can not close the RESULTSET!");
+					e.printStackTrace();
+				}
+			}
+		}
+	}
+
+	private static class ServiceConnectionTest implements ServiceConnection {
+
+		@Override
+		public int[] getOutputLimit(){
+			return new int[]{1000000,1000000};
+		}
+
+		@Override
+		public LimitUnit[] getOutputLimitType(){
+			return new LimitUnit[]{LimitUnit.bytes,LimitUnit.bytes};
+		}
+
+		@Override
+		public String getProviderName(){
+			return null;
+		}
+
+		@Override
+		public String getProviderDescription(){
+			return null;
+		}
+
+		@Override
+		public boolean isAvailable(){
+			return true;
+		}
+
+		@Override
+		public String getAvailability(){
+			return "AVAILABLE";
+		}
+
+		@Override
+		public int[] getRetentionPeriod(){
+			return null;
+		}
+
+		@Override
+		public int[] getExecutionDuration(){
+			return null;
+		}
+
+		@Override
+		public UserIdentifier getUserIdentifier(){
+			return null;
+		}
+
+		@Override
+		public boolean uploadEnabled(){
+			return false;
+		}
+
+		@Override
+		public int[] getUploadLimit(){
+			return null;
+		}
+
+		@Override
+		public LimitUnit[] getUploadLimitType(){
+			return null;
+		}
+
+		@Override
+		public int getMaxUploadSize(){
+			return 0;
+		}
+
+		@Override
+		public TAPMetadata getTAPMetadata(){
+			return null;
+		}
+
+		@Override
+		public Collection<String> getCoordinateSystems(){
+			return null;
+		}
+
+		@Override
+		public TAPLog getLogger(){
+			return null;
+		}
+
+		@SuppressWarnings("rawtypes")
+		@Override
+		public TAPFactory getFactory(){
+			return null;
+		}
+
+		@Override
+		public TAPFileManager getFileManager(){
+			return null;
+		}
+
+		@Override
+		public Iterator<OutputFormat> getOutputFormats(){
+			return null;
+		}
+
+		@Override
+		public OutputFormat getOutputFormat(String mimeOrAlias){
+			return null;
+		}
+
+	}
+
+}
diff --git a/test/tap/formatter/SVFormatTest.java b/test/tap/formatter/SVFormatTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..f44dda5f4196ab6f24a069b35a64a16f1a0b4b3f
--- /dev/null
+++ b/test/tap/formatter/SVFormatTest.java
@@ -0,0 +1,267 @@
+package tap.formatter;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.BufferedOutputStream;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import tap.ServiceConnection;
+import tap.TAPExecutionReport;
+import tap.TAPFactory;
+import tap.TAPJob;
+import tap.data.ResultSetTableIterator;
+import tap.data.TableIterator;
+import tap.file.TAPFileManager;
+import tap.log.TAPLog;
+import tap.metadata.TAPColumn;
+import tap.metadata.TAPMetadata;
+import tap.metadata.TAPType;
+import tap.metadata.TAPType.TAPDatatype;
+import tap.parameters.TAPParameters;
+import testtools.DBTools;
+import uws.service.UserIdentifier;
+
+/**
+ * <p>Test the SVFormat function {@link SVFormat#writeResult(TableIterator, OutputStream, TAPExecutionReport, Thread)}.</p>
+ * 
+ * <p>2 test ares done: 1 with an overflow and another without.</p>
+ * 
+ * @author Gr&eacute;gory Mantelet (ARI)
+ * @version 2.0 (07/2014)
+ */
+public class SVFormatTest {
+
+	private static Connection conn;
+	private static ServiceConnection serviceConn;
+	private static TAPColumn[] resultingColumns;
+	private static File svFile = new File("/home/gmantele/Desktop/sv_test.txt");
+
+	@BeforeClass
+	public static void setUpBeforeClass() throws Exception{
+		conn = DBTools.createConnection("postgresql", "127.0.0.1", null, "gmantele", "gmantele", "pwd");
+		serviceConn = new ServiceConnectionTest();
+
+		resultingColumns = new TAPColumn[4];
+		resultingColumns[0] = new TAPColumn("ID", new TAPType(TAPDatatype.VARCHAR));
+		resultingColumns[1] = new TAPColumn("ra", new TAPType(TAPDatatype.DOUBLE), "Right ascension", "deg", "pos.eq.ra", null);
+		resultingColumns[2] = new TAPColumn("deg", new TAPType(TAPDatatype.DOUBLE), "Declination", "deg", "pos.eq.dec", null);
+		resultingColumns[3] = new TAPColumn("gmag", new TAPType(TAPDatatype.DOUBLE), "G magnitude", "mag", "phot.mag;em.opt.B", null);
+
+		if (!svFile.exists())
+			svFile.createNewFile();
+	}
+
+	@AfterClass
+	public static void tearDownAfterClass() throws Exception{
+		DBTools.closeConnection(conn);
+		svFile.delete();
+	}
+
+	@Test
+	public void testWriteResult(){
+		try{
+			ResultSet rs = DBTools.select(conn, "SELECT id, ra, deg, gmag FROM gums LIMIT 10;");
+
+			HashMap<String,Object> tapParams = new HashMap<String,Object>(1);
+			tapParams.put(TAPJob.PARAM_MAX_REC, "100");
+			TAPParameters params = new TAPParameters(serviceConn, tapParams);
+			TAPExecutionReport report = new TAPExecutionReport("123456A", true, params);
+			report.resultingColumns = resultingColumns;
+
+			TableIterator it = new ResultSetTableIterator(rs);
+
+			SVFormat formatter = new SVFormat(serviceConn, SVFormat.COMMA_SEPARATOR);
+			OutputStream output = new BufferedOutputStream(new FileOutputStream(svFile));
+			formatter.writeResult(it, output, report, Thread.currentThread());
+			output.close();
+
+			String[] cmd = new String[]{"/bin/sh","-c","wc -l < \"" + svFile.getAbsolutePath() + "\""};
+			assertTrue(executeCommand(cmd).trim().equals("11"));
+
+		}catch(Exception t){
+			t.printStackTrace();
+			fail("Unexpected exception!");
+		}
+	}
+
+	@Test
+	public void testWriteResultWithOverflow(){
+		ResultSet rs = null;
+		try{
+			rs = DBTools.select(conn, "SELECT id, ra, deg, gmag FROM gums LIMIT 10;");
+
+			HashMap<String,Object> tapParams = new HashMap<String,Object>(1);
+			tapParams.put(TAPJob.PARAM_MAX_REC, "5");
+			TAPParameters params = new TAPParameters(serviceConn, tapParams);
+			TAPExecutionReport report = new TAPExecutionReport("123456A", true, params);
+			report.resultingColumns = resultingColumns;
+
+			TableIterator it = new ResultSetTableIterator(rs);
+
+			SVFormat formatter = new SVFormat(serviceConn, SVFormat.COMMA_SEPARATOR);
+			OutputStream output = new BufferedOutputStream(new FileOutputStream(svFile));
+			formatter.writeResult(it, output, report, Thread.currentThread());
+			output.close();
+
+			String[] cmd = new String[]{"/bin/sh","-c","wc -l < \"" + svFile.getAbsolutePath() + "\""};
+			assertTrue(executeCommand(cmd).trim().equals("6"));
+
+		}catch(Exception t){
+			t.printStackTrace();
+			fail("Unexpected exception!");
+		}finally{
+			if (rs != null){
+				try{
+					rs.close();
+				}catch(SQLException e){
+					System.err.println("Can not close the RESULTSET!");
+					e.printStackTrace();
+				}
+			}
+		}
+	}
+
+	private String executeCommand(String[] command){
+
+		StringBuffer output = new StringBuffer();
+
+		Process p;
+		try{
+			p = Runtime.getRuntime().exec(command);
+			p.waitFor();
+			BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
+
+			String line = "";
+			while((line = reader.readLine()) != null){
+				output.append(line + "\n");
+			}
+
+		}catch(Exception e){
+			e.printStackTrace();
+		}
+
+		return output.toString();
+
+	}
+
+	private static class ServiceConnectionTest implements ServiceConnection {
+
+		@Override
+		public int[] getOutputLimit(){
+			return new int[]{1000000,1000000};
+		}
+
+		@Override
+		public LimitUnit[] getOutputLimitType(){
+			return new LimitUnit[]{LimitUnit.bytes,LimitUnit.bytes};
+		}
+
+		@Override
+		public String getProviderName(){
+			return null;
+		}
+
+		@Override
+		public String getProviderDescription(){
+			return null;
+		}
+
+		@Override
+		public boolean isAvailable(){
+			return true;
+		}
+
+		@Override
+		public String getAvailability(){
+			return "AVAILABLE";
+		}
+
+		@Override
+		public int[] getRetentionPeriod(){
+			return null;
+		}
+
+		@Override
+		public int[] getExecutionDuration(){
+			return null;
+		}
+
+		@Override
+		public UserIdentifier getUserIdentifier(){
+			return null;
+		}
+
+		@Override
+		public boolean uploadEnabled(){
+			return false;
+		}
+
+		@Override
+		public int[] getUploadLimit(){
+			return null;
+		}
+
+		@Override
+		public LimitUnit[] getUploadLimitType(){
+			return null;
+		}
+
+		@Override
+		public int getMaxUploadSize(){
+			return 0;
+		}
+
+		@Override
+		public TAPMetadata getTAPMetadata(){
+			return null;
+		}
+
+		@Override
+		public Collection<String> getCoordinateSystems(){
+			return null;
+		}
+
+		@Override
+		public TAPLog getLogger(){
+			return null;
+		}
+
+		@SuppressWarnings("rawtypes")
+		@Override
+		public TAPFactory getFactory(){
+			return null;
+		}
+
+		@Override
+		public TAPFileManager getFileManager(){
+			return null;
+		}
+
+		@Override
+		public Iterator<OutputFormat> getOutputFormats(){
+			return null;
+		}
+
+		@Override
+		public OutputFormat getOutputFormat(String mimeOrAlias){
+			return null;
+		}
+
+	}
+
+}
diff --git a/test/tap/formatter/TextFormatTest.java b/test/tap/formatter/TextFormatTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..27b5f38302285a7548435b1b5f58babb6497259c
--- /dev/null
+++ b/test/tap/formatter/TextFormatTest.java
@@ -0,0 +1,267 @@
+package tap.formatter;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.BufferedOutputStream;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import tap.ServiceConnection;
+import tap.TAPExecutionReport;
+import tap.TAPFactory;
+import tap.TAPJob;
+import tap.data.ResultSetTableIterator;
+import tap.data.TableIterator;
+import tap.file.TAPFileManager;
+import tap.log.TAPLog;
+import tap.metadata.TAPColumn;
+import tap.metadata.TAPMetadata;
+import tap.metadata.TAPType;
+import tap.metadata.TAPType.TAPDatatype;
+import tap.parameters.TAPParameters;
+import testtools.DBTools;
+import uws.service.UserIdentifier;
+
+/**
+ * <p>Test the TestFormat function {@link TestFormat#writeResult(TableIterator, OutputStream, TAPExecutionReport, Thread)}.</p>
+ * 
+ * <p>2 test ares done: 1 with an overflow and another without.</p>
+ * 
+ * @author Gr&eacute;gory Mantelet (ARI)
+ * @version 2.0 (07/2014)
+ */
+public class TextFormatTest {
+
+	private static Connection conn;
+	private static ServiceConnection serviceConn;
+	private static TAPColumn[] resultingColumns;
+	private static File textFile = new File("/home/gmantele/Desktop/text_test.txt");
+
+	@BeforeClass
+	public static void setUpBeforeClass() throws Exception{
+		conn = DBTools.createConnection("postgresql", "127.0.0.1", null, "gmantele", "gmantele", "pwd");
+		serviceConn = new ServiceConnectionTest();
+
+		resultingColumns = new TAPColumn[4];
+		resultingColumns[0] = new TAPColumn("ID", new TAPType(TAPDatatype.VARCHAR));
+		resultingColumns[1] = new TAPColumn("ra", new TAPType(TAPDatatype.DOUBLE), "Right ascension", "deg", "pos.eq.ra", null);
+		resultingColumns[2] = new TAPColumn("deg", new TAPType(TAPDatatype.DOUBLE), "Declination", "deg", "pos.eq.dec", null);
+		resultingColumns[3] = new TAPColumn("gmag", new TAPType(TAPDatatype.DOUBLE), "G magnitude", "mag", "phot.mag;em.opt.B", null);
+
+		if (!textFile.exists())
+			textFile.createNewFile();
+	}
+
+	@AfterClass
+	public static void tearDownAfterClass() throws Exception{
+		DBTools.closeConnection(conn);
+		//textFile.delete();
+	}
+
+	@Test
+	public void testWriteResult(){
+		try{
+			ResultSet rs = DBTools.select(conn, "SELECT id, ra, deg, gmag FROM gums LIMIT 10;");
+
+			HashMap<String,Object> tapParams = new HashMap<String,Object>(1);
+			tapParams.put(TAPJob.PARAM_MAX_REC, "100");
+			TAPParameters params = new TAPParameters(serviceConn, tapParams);
+			TAPExecutionReport report = new TAPExecutionReport("123456A", true, params);
+			report.resultingColumns = resultingColumns;
+
+			TableIterator it = new ResultSetTableIterator(rs);
+
+			TextFormat formatter = new TextFormat(serviceConn);
+			OutputStream output = new BufferedOutputStream(new FileOutputStream(textFile));
+			formatter.writeResult(it, output, report, Thread.currentThread());
+			output.close();
+
+			String[] cmd = new String[]{"/bin/sh","-c","wc -l < \"" + textFile.getAbsolutePath() + "\""};
+			assertTrue(executeCommand(cmd).trim().equals("12"));
+
+		}catch(Exception t){
+			t.printStackTrace();
+			fail("Unexpected exception!");
+		}
+	}
+
+	@Test
+	public void testWriteResultWithOverflow(){
+		ResultSet rs = null;
+		try{
+			rs = DBTools.select(conn, "SELECT id, ra, deg, gmag FROM gums LIMIT 10;");
+
+			HashMap<String,Object> tapParams = new HashMap<String,Object>(1);
+			tapParams.put(TAPJob.PARAM_MAX_REC, "5");
+			TAPParameters params = new TAPParameters(serviceConn, tapParams);
+			TAPExecutionReport report = new TAPExecutionReport("123456A", true, params);
+			report.resultingColumns = resultingColumns;
+
+			TableIterator it = new ResultSetTableIterator(rs);
+
+			TextFormat formatter = new TextFormat(serviceConn);
+			OutputStream output = new BufferedOutputStream(new FileOutputStream(textFile));
+			formatter.writeResult(it, output, report, Thread.currentThread());
+			output.close();
+
+			String[] cmd = new String[]{"/bin/sh","-c","wc -l < \"" + textFile.getAbsolutePath() + "\""};
+			assertTrue(executeCommand(cmd).trim().equals("7"));
+
+		}catch(Exception t){
+			t.printStackTrace();
+			fail("Unexpected exception!");
+		}finally{
+			if (rs != null){
+				try{
+					rs.close();
+				}catch(SQLException e){
+					System.err.println("Can not close the RESULTSET!");
+					e.printStackTrace();
+				}
+			}
+		}
+	}
+
+	private String executeCommand(String[] command){
+
+		StringBuffer output = new StringBuffer();
+
+		Process p;
+		try{
+			p = Runtime.getRuntime().exec(command);
+			p.waitFor();
+			BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
+
+			String line = "";
+			while((line = reader.readLine()) != null){
+				output.append(line + "\n");
+			}
+
+		}catch(Exception e){
+			e.printStackTrace();
+		}
+
+		return output.toString();
+
+	}
+
+	private static class ServiceConnectionTest implements ServiceConnection {
+
+		@Override
+		public int[] getOutputLimit(){
+			return new int[]{1000000,1000000};
+		}
+
+		@Override
+		public LimitUnit[] getOutputLimitType(){
+			return new LimitUnit[]{LimitUnit.bytes,LimitUnit.bytes};
+		}
+
+		@Override
+		public String getProviderName(){
+			return null;
+		}
+
+		@Override
+		public String getProviderDescription(){
+			return null;
+		}
+
+		@Override
+		public boolean isAvailable(){
+			return true;
+		}
+
+		@Override
+		public String getAvailability(){
+			return "AVAILABLE";
+		}
+
+		@Override
+		public int[] getRetentionPeriod(){
+			return null;
+		}
+
+		@Override
+		public int[] getExecutionDuration(){
+			return null;
+		}
+
+		@Override
+		public UserIdentifier getUserIdentifier(){
+			return null;
+		}
+
+		@Override
+		public boolean uploadEnabled(){
+			return false;
+		}
+
+		@Override
+		public int[] getUploadLimit(){
+			return null;
+		}
+
+		@Override
+		public LimitUnit[] getUploadLimitType(){
+			return null;
+		}
+
+		@Override
+		public int getMaxUploadSize(){
+			return 0;
+		}
+
+		@Override
+		public TAPMetadata getTAPMetadata(){
+			return null;
+		}
+
+		@Override
+		public Collection<String> getCoordinateSystems(){
+			return null;
+		}
+
+		@Override
+		public TAPLog getLogger(){
+			return null;
+		}
+
+		@SuppressWarnings("rawtypes")
+		@Override
+		public TAPFactory getFactory(){
+			return null;
+		}
+
+		@Override
+		public TAPFileManager getFileManager(){
+			return null;
+		}
+
+		@Override
+		public Iterator<OutputFormat> getOutputFormats(){
+			return null;
+		}
+
+		@Override
+		public OutputFormat getOutputFormat(String mimeOrAlias){
+			return null;
+		}
+
+	}
+
+}
diff --git a/test/tap/formatter/VOTableFormatTest.java b/test/tap/formatter/VOTableFormatTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..f5f0d174554b2c8cfbad9fa07f4a55b805172663
--- /dev/null
+++ b/test/tap/formatter/VOTableFormatTest.java
@@ -0,0 +1,275 @@
+package tap.formatter;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.BufferedOutputStream;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import tap.ServiceConnection;
+import tap.TAPExecutionReport;
+import tap.TAPFactory;
+import tap.TAPJob;
+import tap.data.ResultSetTableIterator;
+import tap.data.TableIterator;
+import tap.file.TAPFileManager;
+import tap.log.TAPLog;
+import tap.metadata.TAPColumn;
+import tap.metadata.TAPMetadata;
+import tap.metadata.TAPType;
+import tap.metadata.TAPType.TAPDatatype;
+import tap.parameters.TAPParameters;
+import testtools.DBTools;
+import uws.service.UserIdentifier;
+
+/**
+ * <p>Test the VOTableFormat function {@link VOTableFormat#writeResult(TableIterator, OutputStream, TAPExecutionReport, Thread)}.</p>
+ * 
+ * <p>2 test ares done: 1 with an overflow and another without.</p>
+ * 
+ * @author Gr&eacute;gory Mantelet (ARI)
+ * @version 2.0 (07/2014)
+ */
+public class VOTableFormatTest {
+
+	private static Connection conn;
+	private static ServiceConnection serviceConn;
+	private static TAPColumn[] resultingColumns;
+	private static File votableFile = new File("/home/gmantele/Desktop/votable_test.xml");
+
+	@BeforeClass
+	public static void setUpBeforeClass() throws Exception{
+		conn = DBTools.createConnection("postgresql", "127.0.0.1", null, "gmantele", "gmantele", "pwd");
+		serviceConn = new ServiceConnectionTest();
+
+		resultingColumns = new TAPColumn[4];
+		resultingColumns[0] = new TAPColumn("ID", new TAPType(TAPDatatype.VARCHAR));
+		resultingColumns[1] = new TAPColumn("ra", new TAPType(TAPDatatype.DOUBLE), "Right ascension", "deg", "pos.eq.ra", null);
+		resultingColumns[2] = new TAPColumn("deg", new TAPType(TAPDatatype.DOUBLE), "Declination", "deg", "pos.eq.dec", null);
+		resultingColumns[3] = new TAPColumn("gmag", new TAPType(TAPDatatype.DOUBLE), "G magnitude", "mag", "phot.mag;em.opt.B", null);
+
+		if (!votableFile.exists())
+			votableFile.createNewFile();
+	}
+
+	@AfterClass
+	public static void tearDownAfterClass() throws Exception{
+		DBTools.closeConnection(conn);
+		votableFile.delete();
+	}
+
+	@Test
+	public void testWriteResult(){
+		try{
+			ResultSet rs = DBTools.select(conn, "SELECT id, ra, deg, gmag FROM gums LIMIT 10;");
+
+			HashMap<String,Object> tapParams = new HashMap<String,Object>(1);
+			tapParams.put(TAPJob.PARAM_MAX_REC, "100");
+			TAPParameters params = new TAPParameters(serviceConn, tapParams);
+			TAPExecutionReport report = new TAPExecutionReport("123456A", true, params);
+			report.resultingColumns = resultingColumns;
+
+			TableIterator it = new ResultSetTableIterator(rs);
+
+			VOTableFormat formatter = new VOTableFormat(serviceConn);
+			OutputStream output = new BufferedOutputStream(new FileOutputStream(votableFile));
+			formatter.writeResult(it, output, report, Thread.currentThread());
+			output.close();
+
+			// note: due to the pipe (|), we must call /bin/sh as a command whose the command to execute in is the "grep ... | wc -l":
+			String[] cmd = new String[]{"/bin/sh","-c","grep \"<TR>\" \"" + votableFile.getAbsolutePath() + "\" | wc -l"};
+			assertTrue(executeCommand(cmd).trim().equals("10"));
+
+			cmd = new String[]{"/bin/sh","-c","grep \"<INFO name=\\\"QUERY_STATUS\\\" value=\\\"OVERFLOW\\\" />\" \"" + votableFile.getAbsolutePath() + "\" | wc -l"};
+			assertTrue(executeCommand(cmd).trim().equals("0"));
+
+		}catch(Exception t){
+			t.printStackTrace();
+			fail("Unexpected exception!");
+		}
+	}
+
+	@Test
+	public void testWriteResultWithOverflow(){
+		ResultSet rs = null;
+		try{
+			rs = DBTools.select(conn, "SELECT id, ra, deg, gmag FROM gums LIMIT 10;");
+
+			HashMap<String,Object> tapParams = new HashMap<String,Object>(1);
+			tapParams.put(TAPJob.PARAM_MAX_REC, "5");
+			TAPParameters params = new TAPParameters(serviceConn, tapParams);
+			TAPExecutionReport report = new TAPExecutionReport("123456A", true, params);
+			report.resultingColumns = resultingColumns;
+
+			TableIterator it = new ResultSetTableIterator(rs);
+
+			VOTableFormat formatter = new VOTableFormat(serviceConn);
+			OutputStream output = new BufferedOutputStream(new FileOutputStream(votableFile));
+			formatter.writeResult(it, output, report, Thread.currentThread());
+			output.close();
+
+			// note: due to the pipe (|), we must call /bin/sh as a command whose the command to execute in is the "grep ... | wc -l":
+			String[] cmd = new String[]{"/bin/sh","-c","grep \"<TR>\" \"" + votableFile.getAbsolutePath() + "\" | wc -l"};
+			assertTrue(executeCommand(cmd).trim().equals("5"));
+
+			cmd = new String[]{"/bin/sh","-c","grep \"<INFO name=\\\"QUERY_STATUS\\\" value=\\\"OVERFLOW\\\" />\" \"" + votableFile.getAbsolutePath() + "\" | wc -l"};
+			assertTrue(executeCommand(cmd).trim().equals("1"));
+
+		}catch(Exception t){
+			t.printStackTrace();
+			fail("Unexpected exception!");
+		}finally{
+			if (rs != null){
+				try{
+					rs.close();
+				}catch(SQLException e){
+					System.err.println("Can not close the RESULTSET!");
+					e.printStackTrace();
+				}
+			}
+		}
+	}
+
+	private String executeCommand(String[] command){
+
+		StringBuffer output = new StringBuffer();
+
+		Process p;
+		try{
+			p = Runtime.getRuntime().exec(command);
+			p.waitFor();
+			BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()));
+
+			String line = "";
+			while((line = reader.readLine()) != null){
+				output.append(line + "\n");
+			}
+
+		}catch(Exception e){
+			e.printStackTrace();
+		}
+
+		return output.toString();
+
+	}
+
+	private static class ServiceConnectionTest implements ServiceConnection {
+
+		@Override
+		public int[] getOutputLimit(){
+			return new int[]{1000000,1000000};
+		}
+
+		@Override
+		public LimitUnit[] getOutputLimitType(){
+			return new LimitUnit[]{LimitUnit.bytes,LimitUnit.bytes};
+		}
+
+		@Override
+		public String getProviderName(){
+			return null;
+		}
+
+		@Override
+		public String getProviderDescription(){
+			return null;
+		}
+
+		@Override
+		public boolean isAvailable(){
+			return true;
+		}
+
+		@Override
+		public String getAvailability(){
+			return "AVAILABLE";
+		}
+
+		@Override
+		public int[] getRetentionPeriod(){
+			return null;
+		}
+
+		@Override
+		public int[] getExecutionDuration(){
+			return null;
+		}
+
+		@Override
+		public UserIdentifier getUserIdentifier(){
+			return null;
+		}
+
+		@Override
+		public boolean uploadEnabled(){
+			return false;
+		}
+
+		@Override
+		public int[] getUploadLimit(){
+			return null;
+		}
+
+		@Override
+		public LimitUnit[] getUploadLimitType(){
+			return null;
+		}
+
+		@Override
+		public int getMaxUploadSize(){
+			return 0;
+		}
+
+		@Override
+		public TAPMetadata getTAPMetadata(){
+			return null;
+		}
+
+		@Override
+		public Collection<String> getCoordinateSystems(){
+			return null;
+		}
+
+		@Override
+		public TAPLog getLogger(){
+			return null;
+		}
+
+		@SuppressWarnings("rawtypes")
+		@Override
+		public TAPFactory getFactory(){
+			return null;
+		}
+
+		@Override
+		public TAPFileManager getFileManager(){
+			return null;
+		}
+
+		@Override
+		public Iterator<OutputFormat> getOutputFormats(){
+			return null;
+		}
+
+		@Override
+		public OutputFormat getOutputFormat(String mimeOrAlias){
+			return null;
+		}
+
+	}
+
+}
diff --git a/test/testtools/DBTools.java b/test/testtools/DBTools.java
index ca032c05a6877a18d34a851be833c24013b96e76..a5ef0fe5d0befee6ed5cab4e61771ce6a5b30a96 100644
--- a/test/testtools/DBTools.java
+++ b/test/testtools/DBTools.java
@@ -1,4 +1,5 @@
 package testtools;
+
 import java.sql.Connection;
 import java.sql.DriverManager;
 import java.sql.ResultSet;
@@ -118,6 +119,9 @@ public final class DBTools {
 				}catch(InterruptedException e){
 					System.err.println("WARNING: can't wait/sleep before testing the connection close status! [" + e.getMessage() + "]");
 				}
+				// TODO DEBUG MSG
+				if (conn.isClosed())
+					System.out.println("[DEBUG] Connection closed!");
 				return conn.isClosed();
 			}else
 				return true;