From 16f7e8e3f25d7ea82e8b1b0fc9d679669b160e04 Mon Sep 17 00:00:00 2001
From: gmantele <gmantele@ari.uni-heidelberg.de>
Date: Thu, 26 Mar 2015 16:04:38 +0100
Subject: [PATCH] [TAP] Add the possibility to set the FetchSize on a DB
 Statement for the execution of ADQL queries. It is also possible to set a
 fetchSize different for synchronous and asynchronous queries.

---
 src/tap/ADQLExecutor.java                     |  13 +-
 src/tap/ServiceConnection.java                |  34 ++++-
 .../config/ConfigurableServiceConnection.java |  46 ++++++
 src/tap/config/TAPConfiguration.java          |   4 +
 src/tap/config/tap_configuration_file.html    |  31 ++++
 src/tap/config/tap_full.properties            |  29 +++-
 src/tap/db/DBConnection.java                  |  39 ++++-
 src/tap/db/JDBCConnection.java                |  81 +++++++++-
 .../TestConfigurableServiceConnection.java    | 111 ++++++++++++--
 .../config/TestConfigurableTAPFactory.java    |   5 +
 test/tap/formatter/JSONFormatTest.java        | 132 +----------------
 test/tap/formatter/SVFormatTest.java          | 132 +----------------
 .../tap/formatter/ServiceConnection4Test.java | 139 ++++++++++++++++++
 test/tap/formatter/TextFormatTest.java        | 132 +----------------
 test/tap/formatter/VOTableFormatTest.java     | 132 +----------------
 .../parameters/ServiceConnectionOfTest.java   |   5 +
 16 files changed, 520 insertions(+), 545 deletions(-)
 create mode 100644 test/tap/formatter/ServiceConnection4Test.java

diff --git a/src/tap/ADQLExecutor.java b/src/tap/ADQLExecutor.java
index 3e19d11..12212b1 100644
--- a/src/tap/ADQLExecutor.java
+++ b/src/tap/ADQLExecutor.java
@@ -356,7 +356,10 @@ public class ADQLExecutor {
 
 			// 4. WRITE RESULT:
 			startStep(ExecutionProgression.WRITING_RESULT);
-			writeResult(queryResult);
+			if (response != null && response.isCommitted())
+				getLogger().logTAP(LogLevel.WARNING, report, "WRITING_RESULT", "HTTP request canceled/timeout! The result can not have been returned to the user.", null);
+			else
+				writeResult(queryResult);
 			endStep();
 
 			// Report the COMPLETED status:
@@ -548,6 +551,14 @@ public class ADQLExecutor {
 		// Log the start of execution:
 		logger.logTAP(LogLevel.INFO, report, "EXECUTING", "Executing ADQL: " + adql.toADQL().replaceAll("(\t|\r?\n)+", " "), null);
 
+		// Set the fetch size, if any:
+		if (service.getFetchSize() != null && service.getFetchSize().length >= 1){
+			if (report.synchronous && service.getFetchSize().length >= 2)
+				dbConn.setFetchSize(service.getFetchSize()[1]);
+			else
+				dbConn.setFetchSize(service.getFetchSize()[0]);
+		}
+
 		// Execute the ADQL query:
 		TableIterator result = dbConn.executeQuery(adql);
 
diff --git a/src/tap/ServiceConnection.java b/src/tap/ServiceConnection.java
index a247826..646db7c 100644
--- a/src/tap/ServiceConnection.java
+++ b/src/tap/ServiceConnection.java
@@ -16,13 +16,14 @@ 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,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ * Copyright 2012-2015 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
  *                       Astronomisches Rechen Institut (ARI)
  */
 
 import java.util.Collection;
 import java.util.Iterator;
 
+import tap.db.DBConnection;
 import tap.formatter.OutputFormat;
 import tap.log.DefaultTAPLog;
 import tap.log.TAPLog;
@@ -42,7 +43,7 @@ import adql.db.FunctionDef;
  * </p>
  * 
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 2.0 (01/2015)
+ * @version 2.0 (03/2015)
  */
 public interface ServiceConnection {
 
@@ -660,4 +661,33 @@ public interface ServiceConnection {
 	 */
 	public OutputFormat getOutputFormat(final String mimeOrAlias);
 
+	/**
+	 * <i>[OPTIONAL]</i>
+	 * <p>Get the size of result blocks to fetch from the database.</p>
+	 * 
+	 * <p>
+	 * 	Rather than fetching a query result in a whole, it may be possible to specify to the database
+	 * 	that results may be retrieved by blocks whose the size can be specified by this function.
+	 * 	If supported by the DBMS and the JDBC driver, this feature may help sparing memory and avoid
+	 * 	too much waiting time from the TAP /sync users (and thus, avoiding some HTTP client timeouts).
+	 * </p>
+	 * 
+	 * <p><i>Note:
+	 * 	Generally, this feature is well supported by DBMS. But for that, the used JDBC driver must use
+	 * 	the V3 protocol. If anyway, this feature is supported neither by the DBMS, the JDBC driver nor your
+	 * 	{@link DBConnection}, no error will be thrown if a value is returned by this function: it will be silently
+	 * 	ignored by the library.
+	 * </i></p>
+	 * 
+	 * @return	<i>null</i> or an array of 1 or 2 integers.
+	 * 			If <i>null</i> (or empty array), no attempt to set fetch size will be done and so, ONLY the default
+	 *        	value of the {@link DBConnection} will be used.
+	 *        	[0]=fetchSize for async queries, [1]=fetchSize for sync queries.
+	 *        	If [1] is omitted, it will be considered as equals to [0].
+	 *        	If a fetchSize is negative or null, the default value of your JDBC driver will be used.
+	 * 
+	 * @since 2.0
+	 */
+	public int[] getFetchSize();
+
 }
diff --git a/src/tap/config/ConfigurableServiceConnection.java b/src/tap/config/ConfigurableServiceConnection.java
index 2cca929..cb13d0f 100644
--- a/src/tap/config/ConfigurableServiceConnection.java
+++ b/src/tap/config/ConfigurableServiceConnection.java
@@ -19,12 +19,15 @@ package tap.config;
  * Copyright 2015 - Astronomisches Rechen Institut (ARI)
  */
 
+import static tap.config.TAPConfiguration.DEFAULT_ASYNC_FETCH_SIZE;
 import static tap.config.TAPConfiguration.DEFAULT_DIRECTORY_PER_USER;
 import static tap.config.TAPConfiguration.DEFAULT_EXECUTION_DURATION;
 import static tap.config.TAPConfiguration.DEFAULT_GROUP_USER_DIRECTORIES;
 import static tap.config.TAPConfiguration.DEFAULT_MAX_ASYNC_JOBS;
 import static tap.config.TAPConfiguration.DEFAULT_RETENTION_PERIOD;
+import static tap.config.TAPConfiguration.DEFAULT_SYNC_FETCH_SIZE;
 import static tap.config.TAPConfiguration.DEFAULT_UPLOAD_MAX_FILE_SIZE;
+import static tap.config.TAPConfiguration.KEY_ASYNC_FETCH_SIZE;
 import static tap.config.TAPConfiguration.KEY_COORD_SYS;
 import static tap.config.TAPConfiguration.KEY_DEFAULT_EXECUTION_DURATION;
 import static tap.config.TAPConfiguration.KEY_DEFAULT_OUTPUT_LIMIT;
@@ -47,6 +50,7 @@ import static tap.config.TAPConfiguration.KEY_MIN_LOG_LEVEL;
 import static tap.config.TAPConfiguration.KEY_OUTPUT_FORMATS;
 import static tap.config.TAPConfiguration.KEY_PROVIDER_NAME;
 import static tap.config.TAPConfiguration.KEY_SERVICE_DESCRIPTION;
+import static tap.config.TAPConfiguration.KEY_SYNC_FETCH_SIZE;
 import static tap.config.TAPConfiguration.KEY_TAP_FACTORY;
 import static tap.config.TAPConfiguration.KEY_UDFS;
 import static tap.config.TAPConfiguration.KEY_UPLOAD_ENABLED;
@@ -148,6 +152,8 @@ public final class ConfigurableServiceConnection implements ServiceConnection {
 	private LimitUnit[] uploadLimitTypes = new LimitUnit[2];
 	private int maxUploadSize = DEFAULT_UPLOAD_MAX_FILE_SIZE;
 
+	private int[] fetchSize = new int[]{DEFAULT_ASYNC_FETCH_SIZE,DEFAULT_SYNC_FETCH_SIZE};
+
 	private UserIdentifier userIdentifier = null;
 
 	private ArrayList<String> lstCoordSys = null;
@@ -191,6 +197,8 @@ public final class ConfigurableServiceConnection implements ServiceConnection {
 		addOutputFormats(tapConfig);
 		// set output limits:
 		initOutputLimits(tapConfig);
+		// set fetch size:
+		initFetchSize(tapConfig);
 
 		// 7. CONFIGURE THE UPLOAD:
 		// is upload enabled ?
@@ -466,6 +474,7 @@ public final class ConfigurableServiceConnection implements ServiceConnection {
 			outputFormats.add(new VOTableFormat(this, DataFormat.TABLEDATA));
 			outputFormats.add(new VOTableFormat(this, DataFormat.FITS));
 			outputFormats.add(new FITSFormat(this));
+			outputFormats.add(new JSONFormat(this));
 			outputFormats.add(new SVFormat(this, ",", true));
 			outputFormats.add(new SVFormat(this, "\t", true));
 			outputFormats.add(new TextFormat(this));
@@ -660,6 +669,38 @@ public final class ConfigurableServiceConnection implements ServiceConnection {
 			throw new TAPException("The default output limit (here: " + outputLimits[0] + ") MUST be less or equal to the maximum output limit (here: " + limit[0] + ")!");
 	}
 
+	private void initFetchSize(final Properties tapConfig) throws TAPException{
+		fetchSize = new int[2];
+
+		// Set the fetch size for asynchronous queries:
+		String propVal = getProperty(tapConfig, KEY_ASYNC_FETCH_SIZE);
+		if (propVal == null)
+			fetchSize[0] = DEFAULT_ASYNC_FETCH_SIZE;
+		else{
+			try{
+				fetchSize[0] = Integer.parseInt(propVal);
+				if (fetchSize[0] < 0)
+					fetchSize[0] = 0;
+			}catch(NumberFormatException nfe){
+				throw new TAPException("Integer expected for the property " + KEY_ASYNC_FETCH_SIZE + ": \"" + propVal + "\"!");
+			}
+		}
+
+		// Set the fetch size for synchronous queries:
+		propVal = getProperty(tapConfig, KEY_SYNC_FETCH_SIZE);
+		if (propVal == null)
+			fetchSize[1] = DEFAULT_SYNC_FETCH_SIZE;
+		else{
+			try{
+				fetchSize[1] = Integer.parseInt(propVal);
+				if (fetchSize[1] < 0)
+					fetchSize[1] = 0;
+			}catch(NumberFormatException nfe){
+				throw new TAPException("Integer expected for the property " + KEY_SYNC_FETCH_SIZE + ": \"" + propVal + "\"!");
+			}
+		}
+	}
+
 	private void initUploadLimits(final Properties tapConfig) throws TAPException{
 		Object[] limit = parseLimit(getProperty(tapConfig, KEY_DEFAULT_UPLOAD_LIMIT), KEY_DEFAULT_UPLOAD_LIMIT, true);
 		uploadLimitTypes[0] = (LimitUnit)limit[1];
@@ -1182,4 +1223,9 @@ public final class ConfigurableServiceConnection implements ServiceConnection {
 		return udfs;
 	}
 
+	@Override
+	public int[] getFetchSize(){
+		return fetchSize;
+	}
+
 }
diff --git a/src/tap/config/TAPConfiguration.java b/src/tap/config/TAPConfiguration.java
index 9a256c0..b9a91b2 100644
--- a/src/tap/config/TAPConfiguration.java
+++ b/src/tap/config/TAPConfiguration.java
@@ -54,6 +54,10 @@ public final class TAPConfiguration {
 	public final static String KEY_SQL_TRANSLATOR = "sql_translator";
 	public final static String VALUE_POSTGRESQL = "postgres";
 	public final static String VALUE_PGSPHERE = "pgsphere";
+	public final static String KEY_SYNC_FETCH_SIZE = "sync_fetch_size";
+	public final static int DEFAULT_SYNC_FETCH_SIZE = 10000;
+	public final static String KEY_ASYNC_FETCH_SIZE = "async_fetch_size";
+	public final static int DEFAULT_ASYNC_FETCH_SIZE = 100000;
 	public final static String KEY_DATASOURCE_JNDI_NAME = "datasource_jndi_name";
 	public final static String KEY_JDBC_DRIVER = "jdbc_driver";
 	public final static HashMap<String,String> VALUE_JDBC_DRIVERS = new HashMap<String,String>(4);
diff --git a/src/tap/config/tap_configuration_file.html b/src/tap/config/tap_configuration_file.html
index 534d88a..381d84e 100644
--- a/src/tap/config/tap_configuration_file.html
+++ b/src/tap/config/tap_configuration_file.html
@@ -172,6 +172,37 @@
 				</td>
 				<td><ul><li>postgres</li><li>pgsphere</li><li>{apackage.MyADQLTranslator}</li></ul></td>
 			</tr>
+			<tr class="optional">
+				<td class="done">sync_fetch_size</td>
+				<td></td>
+				<td>integer</td>
+				<td>
+					<p>Size of result blocks to fetch from the database when a ADQL query is executed in <em>Synchronous mode</em>.</p>
+					<p>Rather than fetching a query result in a whole, it may be possible to specify to the database
+	 				that results may be retrieved by blocks whose the size can be specified with this property.
+	 				If supported by the DBMS and the JDBC driver, this feature may help sparing memory and avoid
+	 				too much waiting time from the TAP /sync users (and thus, avoiding some HTTP client timeouts).</p>
+	 				<p>A negative or null value means that the default value of the JDBC driver will be used. Generally,
+	 				it means that the database must wait to have collected all data before sending them to the library.</p>
+					<p><em>Default: <code>sync_fetch_size=10000</code></em></p>
+				</td>
+				<td><ul><li>10000 <em>(default)</em><li>0 <em>(wait for the the whole result)</em></li><li>100000</li></ul></td>
+			</tr>
+			<tr class="optional">
+				<td class="done">async_fetch_size</td>
+				<td></td>
+				<td>integer</td>
+				<td>
+					<p>Size of result blocks to fetch from the database when an ADQL query is executed in <em>Asynchronous mode</em>.</p>
+					<p>Rather than fetching a query result in a whole, it may be possible to specify to the database
+	 				that results may be retrieved by blocks whose the size can be specified with this property.
+	 				If supported by the DBMS and the JDBC driver, this feature may help sparing memory.</p>
+	 				<p>A negative or null value means that the default value of the JDBC driver will be used. Generally,
+	 				it means that the database must wait to have collected all data before sending them to the library.</p>
+					<p><em>Default: <code>async_fetch_size=100000</code></em></p>
+				</td>
+				<td><ul><li>100000 <em>(default)</em><li>0 <em>(wait for the the whole result)</em></li><li>1000000</li></ul></td>
+			</tr>
 			
 			<tr><td colspan="5">&#10551; JNDI datasource <i>(only if database_access=jndi)</i></td></tr>
 			<tr class="mandatory">
diff --git a/src/tap/config/tap_full.properties b/src/tap/config/tap_full.properties
index 1e48008..38e24e7 100644
--- a/src/tap/config/tap_full.properties
+++ b/src/tap/config/tap_full.properties
@@ -2,7 +2,7 @@
 #             FULL TAP CONFIGURATION FILE                #
 #                                                        #
 # TAP Version: 2.0                                       #
-# Date: 11 Mars 2015                                     #
+# Date: 26 Mars 2015                                     #
 # Author: Gregory Mantelet (ARI)                         #
 #                                                        #
 ########################################################## 
@@ -69,6 +69,33 @@ database_access =
 # Allowed values: postgres, pgsphere, a class name
 sql_translator = postgres
 
+# [OPTIONAL]
+# Size of result blocks to fetch from the database when a ADQL query is executed in Synchronous mode.
+# 
+# Rather than fetching a query result in a whole, it may be possible to specify to the database that
+# results may be retrieved by blocks whose the size can be specified with this property. If supported by
+# the DBMS and the JDBC driver, this feature may help sparing memory and avoid too much waiting time from
+# the TAP /sync users (and thus, avoiding some HTTP client timeouts).
+# 
+# A negative or null value means that the default value of the JDBC driver will be used. Generally, it means
+# that the database must wait to have collected all data before sending them to the library.
+# 
+# Default: sync_fetch_size=10000
+sync_fetch_size = 10000
+
+# [OPTIONAL]
+# Size of result blocks to fetch from the database when an ADQL query is executed in Asynchronous mode.
+# 
+# Rather than fetching a query result in a whole, it may be possible to specify to the database that
+# results may be retrieved by blocks whose the size can be specified with this property. If supported by
+# the DBMS and the JDBC driver, this feature may help sparing memory.
+# 
+# A negative or null value means that the default value of the JDBC driver will be used. Generally, it means
+# that the database must wait to have collected all data before sending them to the library.
+# 
+# Default: async_fetch_size=100000
+async_fetch_size=100000
+
 #############################
 # IF DATABASE ACCESS = JNDI #
 #############################
diff --git a/src/tap/db/DBConnection.java b/src/tap/db/DBConnection.java
index 45931a6..929d80a 100644
--- a/src/tap/db/DBConnection.java
+++ b/src/tap/db/DBConnection.java
@@ -16,7 +16,7 @@ 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,2014 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
+ * Copyright 2012-2015 - UDS/Centre de Données astronomiques de Strasbourg (CDS),
  *                       Astronomisches Rechen Institut (ARI)
  */
 
@@ -43,7 +43,7 @@ import adql.translator.ADQLTranslator;
  * </p>
  * 
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 2.0 (09/2014)
+ * @version 2.0 (03/2015)
  */
 public interface DBConnection {
 
@@ -235,4 +235,39 @@ public interface DBConnection {
 	 */
 	public TableIterator executeQuery(final ADQLQuery adqlQuery) throws DBException;
 
+	/**
+	 * <p>Set the number of rows to fetch before searching/getting the following.
+	 * Thus, rows are fetched by block whose the size is set by this function.</p>
+	 * 
+	 * <p>
+	 * 	<i>This feature may not be supported.</i> In such case or if an exception occurs while setting the fetch size,
+	 * 	this function must not send any exception and the connection stays with its default fetch size. A message may be however
+	 * 	logged.
+	 * </p>
+	 * 
+	 * <p><i>Note:
+	 * 	The "fetch size" should be taken into account only for SELECT queries executed by {@link #executeQuery(ADQLQuery)}.
+	 * </i></p>
+	 * 
+	 * <p>
+	 * 	This feature is generally implemented by JDBC drivers using the V3 protocol. Thus, here is how the PostgreSQL JDBC documentation
+	 * 	(https://jdbc.postgresql.org/documentation/head/query.html#query-with-cursor) describes this feature:
+	 * </p>
+	 * <blockquote>
+	 *	 <p>
+	 * 		By default the driver collects all the results for the query at once. This can be inconvenient for large data sets
+	 * 		so the JDBC driver provides a means of basing a ResultSet on a database cursor and only fetching a small number of rows.
+	 * 	</p>
+	 * 	<p>
+	 * 		A small number of rows are cached on the client side of the connection and when exhausted the next block of rows
+	 * 		is retrieved by repositioning the cursor.
+	 * 	</p>
+	 * </blockquote>
+	 * 
+	 * @param size	Blocks size (in number of rows) to fetch.
+	 * 
+	 * @since 2.0
+	 */
+	public void setFetchSize(final int size);
+
 }
diff --git a/src/tap/db/JDBCConnection.java b/src/tap/db/JDBCConnection.java
index 0ea1653..52d0475 100644
--- a/src/tap/db/JDBCConnection.java
+++ b/src/tap/db/JDBCConnection.java
@@ -120,8 +120,32 @@ import adql.translator.TranslationException;
  * 	and managed.
  * </p>
  * 
+ * <h3>Fetch size</h3>
+ * 
+ * <p>
+ * 	The possibility to specify a "fetch size" to the JDBC driver (and more exactly to a {@link Statement}) may reveal
+ * 	very helpful when dealing with large datasets. Thus, it is possible to fetch rows by block of a size represented
+ * 	by this "fetch size". This is also possible with this {@link DBConnection} thanks to the function {@link #setFetchSize(int)}.
+ * </p>
+ * 
+ * <p>
+ * 	However, some JDBC driver or DBMS may not support this feature. In such case, it is then automatically disabled by
+ * 	{@link JDBCConnection} so that any subsequent queries do not attempt to use it again. The {@link #supportsFetchSize}
+ * 	is however reset to <code>true</code> when {@link #setFetchSize(int)} is called.
+ * </p>
+ * 
+ * <p><i>Note 1:
+ * 	The "fetch size" feature is used only for SELECT queries executed by {@link #executeQuery(ADQLQuery)}. In all other functions,
+ * 	results of SELECT queries are fetched with the default parameter of the JDBC driver and its {@link Statement} implementation.
+ * </i></p>
+ * 
+ * <p><i>Note 2:
+ * 	By default, this feature is disabled. So the default value of the JDBC driver is used.
+ * 	To enable it, a simple call to {@link #setFetchSize(int)} is enough, whatever is the given value.
+ * </i></p>
+ * 
  * @author Gr&eacute;gory Mantelet (CDS;ARI)
- * @version 2.0 (02/2015)
+ * @version 2.0 (03/2015)
  * @since 2.0
  */
 public class JDBCConnection implements DBConnection {
@@ -193,6 +217,27 @@ public class JDBCConnection implements DBConnection {
 	/** Indicate whether the quoted identifiers are stored in upper case in the DBMS. */
 	protected boolean upperCaseQuoted;
 
+	/* FETCH SIZE */
+
+	/** Special fetch size meaning that the JDBC driver is free to set its own guess for this value. */
+	public final static int IGNORE_FETCH_SIZE = 0;
+	/** Default fetch size.
+	 * <i>Note 1: this value may be however ignored if the JDBC driver does not support this feature.</i>
+	 * <i>Note 2: by default set to {@link #IGNORE_FETCH_SIZE}.</i> */
+	public final static int DEFAULT_FETCH_SIZE = IGNORE_FETCH_SIZE;
+
+	/** <p>Indicate whether the last fetch size operation works.</p>
+	 * <p>By default, this attribute is set to <code>false</code>, meaning that the "fetch size" feature is
+	 * disabled. To enable it, a simple call to {@link #setFetchSize(int)} is enough, whatever is the given value.</p>
+	 * <p>If just once this operation fails, the fetch size feature will be always considered as unsupported in this {@link JDBCConnection}
+	 * until the next call of {@link #setFetchSize(int)}.</p> */
+	protected boolean supportsFetchSize = false;
+
+	/** <p>Fetch size to set in the {@link Statement} in charge of executing a SELECT query.</p>
+	 * <p><i>Note 1: this value must always be positive. If negative or null, it will be ignored and the {@link Statement} will keep its default behavior.</i></p>
+	 * <p><i>Note 2: if this feature is enabled (i.e. has a value &gt; 0), the AutoCommit will be disabled.</i></p> */
+	protected int fetchSize = DEFAULT_FETCH_SIZE;
+
 	/**
 	 * <p>Creates a JDBC connection to the specified database and with the specified JDBC driver.
 	 * This connection is established using the given user name and password.<p>
@@ -366,13 +411,33 @@ public class JDBCConnection implements DBConnection {
 				logger.logDB(LogLevel.INFO, this, "TRANSLATE", "Translating ADQL: " + adqlQuery.toADQL().replaceAll("(\t|\r?\n)+", " "), null);
 			sql = translator.translate(adqlQuery);
 
-			// 2. Execute the SQL query:
-			Statement stmt = connection.createStatement();
+			// 2. Create the statement and if needed, configure it for the given fetch size:
+			if (supportsFetchSize && fetchSize > 0){
+				try{
+					connection.setAutoCommit(false);
+				}catch(SQLException se){
+					supportsFetchSize = false;
+					if (logger != null)
+						logger.logDB(LogLevel.WARNING, this, "RESULT", "Fetch size unsupported!", null);
+				}
+			}
+			Statement stmt = connection.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
+			if (supportsFetchSize){
+				try{
+					stmt.setFetchSize(fetchSize);
+				}catch(SQLException se){
+					supportsFetchSize = false;
+					if (logger != null)
+						logger.logDB(LogLevel.WARNING, this, "RESULT", "Fetch size unsupported!", null);
+				}
+			}
+
+			// 3. Execute the SQL query:
+			result = stmt.executeQuery(sql);
 			if (logger != null)
 				logger.logDB(LogLevel.INFO, this, "EXECUTE", "Executing translated query: " + sql.replaceAll("(\t|\r?\n)+", " "), null);
-			result = stmt.executeQuery(sql);
 
-			// 3. Return the result through a TableIterator object:
+			// 4. Return the result through a TableIterator object:
 			if (logger != null)
 				logger.logDB(LogLevel.INFO, this, "RESULT", "Returning result", null);
 			return createTableIterator(result, adqlQuery.getResultingColumns());
@@ -2700,4 +2765,10 @@ public class JDBCConnection implements DBConnection {
 				return dbName.equalsIgnoreCase(metaName);
 		}
 	}
+
+	@Override
+	public void setFetchSize(final int size){
+		supportsFetchSize = true;
+		fetchSize = (size > 0) ? size : IGNORE_FETCH_SIZE;
+	}
 }
diff --git a/test/tap/config/TestConfigurableServiceConnection.java b/test/tap/config/TestConfigurableServiceConnection.java
index 006bd5e..98396df 100644
--- a/test/tap/config/TestConfigurableServiceConnection.java
+++ b/test/tap/config/TestConfigurableServiceConnection.java
@@ -6,7 +6,10 @@ import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static tap.config.TAPConfiguration.DEFAULT_ASYNC_FETCH_SIZE;
 import static tap.config.TAPConfiguration.DEFAULT_MAX_ASYNC_JOBS;
+import static tap.config.TAPConfiguration.DEFAULT_SYNC_FETCH_SIZE;
+import static tap.config.TAPConfiguration.KEY_ASYNC_FETCH_SIZE;
 import static tap.config.TAPConfiguration.KEY_COORD_SYS;
 import static tap.config.TAPConfiguration.KEY_DEFAULT_OUTPUT_LIMIT;
 import static tap.config.TAPConfiguration.KEY_FILE_MANAGER;
@@ -18,6 +21,7 @@ import static tap.config.TAPConfiguration.KEY_METADATA;
 import static tap.config.TAPConfiguration.KEY_METADATA_FILE;
 import static tap.config.TAPConfiguration.KEY_MIN_LOG_LEVEL;
 import static tap.config.TAPConfiguration.KEY_OUTPUT_FORMATS;
+import static tap.config.TAPConfiguration.KEY_SYNC_FETCH_SIZE;
 import static tap.config.TAPConfiguration.KEY_TAP_FACTORY;
 import static tap.config.TAPConfiguration.KEY_UDFS;
 import static tap.config.TAPConfiguration.KEY_USER_IDENTIFIER;
@@ -86,16 +90,19 @@ public class TestConfigurableServiceConnection {
 			badVotFormat5Prop, badVotFormat6Prop, unknownFormatProp,
 			maxAsyncProp, negativeMaxAsyncProp, notIntMaxAsyncProp,
 			defaultOutputLimitProp, maxOutputLimitProp,
-			bothOutputLimitGoodProp, bothOutputLimitBadProp, userIdentProp,
-			notClassPathUserIdentProp, coordSysProp, noneCoordSysProp,
-			anyCoordSysProp, noneInsideCoordSysProp, unknownCoordSysProp,
-			geometriesProp, noneGeomProp, anyGeomProp, noneInsideGeomProp,
-			unknownGeomProp, anyUdfsProp, noneUdfsProp, udfsProp,
-			udfsWithClassNameProp, udfsListWithNONEorANYProp,
-			udfsWithWrongParamLengthProp, udfsWithMissingBracketsProp,
-			udfsWithMissingDefProp1, udfsWithMissingDefProp2,
-			emptyUdfItemProp1, emptyUdfItemProp2, udfWithMissingEndBracketProp,
-			customFactoryProp, badCustomFactoryProp;
+			bothOutputLimitGoodProp, bothOutputLimitBadProp, syncFetchSizeProp,
+			notIntSyncFetchSizeProp, negativeSyncFetchSizeProp,
+			notIntAsyncFetchSizeProp, negativeAsyncFetchSizeProp,
+			asyncFetchSizeProp, userIdentProp, notClassPathUserIdentProp,
+			coordSysProp, noneCoordSysProp, anyCoordSysProp,
+			noneInsideCoordSysProp, unknownCoordSysProp, geometriesProp,
+			noneGeomProp, anyGeomProp, noneInsideGeomProp, unknownGeomProp,
+			anyUdfsProp, noneUdfsProp, udfsProp, udfsWithClassNameProp,
+			udfsListWithNONEorANYProp, udfsWithWrongParamLengthProp,
+			udfsWithMissingBracketsProp, udfsWithMissingDefProp1,
+			udfsWithMissingDefProp2, emptyUdfItemProp1, emptyUdfItemProp2,
+			udfWithMissingEndBracketProp, customFactoryProp,
+			badCustomFactoryProp;
 
 	@BeforeClass
 	public static void setUp() throws Exception{
@@ -198,6 +205,24 @@ public class TestConfigurableServiceConnection {
 		bothOutputLimitBadProp.setProperty(KEY_DEFAULT_OUTPUT_LIMIT, "1000");
 		bothOutputLimitBadProp.setProperty(KEY_MAX_OUTPUT_LIMIT, "100");
 
+		syncFetchSizeProp = (Properties)validProp.clone();
+		syncFetchSizeProp.setProperty(KEY_SYNC_FETCH_SIZE, "50");
+
+		notIntSyncFetchSizeProp = (Properties)validProp.clone();
+		notIntSyncFetchSizeProp.setProperty(KEY_SYNC_FETCH_SIZE, "foo");
+
+		negativeSyncFetchSizeProp = (Properties)validProp.clone();
+		negativeSyncFetchSizeProp.setProperty(KEY_SYNC_FETCH_SIZE, "-3");
+
+		asyncFetchSizeProp = (Properties)validProp.clone();
+		asyncFetchSizeProp.setProperty(KEY_ASYNC_FETCH_SIZE, "50");
+
+		notIntAsyncFetchSizeProp = (Properties)validProp.clone();
+		notIntAsyncFetchSizeProp.setProperty(KEY_ASYNC_FETCH_SIZE, "foo");
+
+		negativeAsyncFetchSizeProp = (Properties)validProp.clone();
+		negativeAsyncFetchSizeProp.setProperty(KEY_ASYNC_FETCH_SIZE, "-3");
+
 		userIdentProp = (Properties)validProp.clone();
 		userIdentProp.setProperty(KEY_USER_IDENTIFIER, "{tap.config.TestConfigurableServiceConnection$UserIdentifierTest}");
 
@@ -320,6 +345,10 @@ public class TestConfigurableServiceConnection {
 			assertNull(connection.getUserIdentifier());
 			assertNull(connection.getGeometries());
 			assertEquals(0, connection.getUDFs().size());
+			assertNotNull(connection.getFetchSize());
+			assertEquals(2, connection.getFetchSize().length);
+			assertEquals(DEFAULT_ASYNC_FETCH_SIZE, connection.getFetchSize()[0]);
+			assertEquals(DEFAULT_SYNC_FETCH_SIZE, connection.getFetchSize()[1]);
 
 			// finally, save metadata in an XML file for the other tests:
 			writer = new PrintWriter(new File(XML_FILE));
@@ -352,6 +381,10 @@ public class TestConfigurableServiceConnection {
 			assertNull(connection.getUserIdentifier());
 			assertNull(connection.getGeometries());
 			assertEquals(0, connection.getUDFs().size());
+			assertNotNull(connection.getFetchSize());
+			assertEquals(2, connection.getFetchSize().length);
+			assertEquals(DEFAULT_ASYNC_FETCH_SIZE, connection.getFetchSize()[0]);
+			assertEquals(DEFAULT_SYNC_FETCH_SIZE, connection.getFetchSize()[1]);
 		}catch(Exception e){
 			fail("This MUST have succeeded because the property file is valid! \nCaught exception: " + getPertinentMessage(e));
 		}
@@ -709,6 +742,64 @@ public class TestConfigurableServiceConnection {
 			assertEquals("The default output limit (here: 1000) MUST be less or equal to the maximum output limit (here: 100)!", e.getMessage());
 		}
 
+		// Test with a not integer sync. fetch size:
+		try{
+			new ConfigurableServiceConnection(notIntSyncFetchSizeProp);
+			fail("This MUST have failed because the set sync. fetch size is not an integer!");
+		}catch(Exception e){
+			assertEquals(TAPException.class, e.getClass());
+			assertEquals("Integer expected for the property " + KEY_SYNC_FETCH_SIZE + ": \"foo\"!", e.getMessage());
+		}
+
+		// Test with a negative sync. fetch size:
+		try{
+			ServiceConnection connection = new ConfigurableServiceConnection(negativeSyncFetchSizeProp);
+			assertNotNull(connection.getFetchSize());
+			assertTrue(connection.getFetchSize().length >= 2);
+			assertEquals(connection.getFetchSize()[1], 0);
+		}catch(Exception e){
+			fail("This MUST have succeeded because a negative fetch size must be set by default to 0 (meaning default JDBC driver value)! \nCaught exception: " + getPertinentMessage(e));
+		}
+
+		// Test with any valid sync. fetch size:
+		try{
+			ServiceConnection connection = new ConfigurableServiceConnection(syncFetchSizeProp);
+			assertNotNull(connection.getFetchSize());
+			assertTrue(connection.getFetchSize().length >= 2);
+			assertEquals(connection.getFetchSize()[1], 50);
+		}catch(Exception e){
+			fail("This MUST have succeeded because a valid fetch size has been provided! \nCaught exception: " + getPertinentMessage(e));
+		}
+
+		// Test with a not integer async. fetch size:
+		try{
+			new ConfigurableServiceConnection(notIntAsyncFetchSizeProp);
+			fail("This MUST have failed because the set async. fetch size is not an integer!");
+		}catch(Exception e){
+			assertEquals(TAPException.class, e.getClass());
+			assertEquals("Integer expected for the property " + KEY_ASYNC_FETCH_SIZE + ": \"foo\"!", e.getMessage());
+		}
+
+		// Test with a negative async. fetch size:
+		try{
+			ServiceConnection connection = new ConfigurableServiceConnection(negativeAsyncFetchSizeProp);
+			assertNotNull(connection.getFetchSize());
+			assertTrue(connection.getFetchSize().length >= 1);
+			assertEquals(connection.getFetchSize()[0], 0);
+		}catch(Exception e){
+			fail("This MUST have succeeded because a negative fetch size must be set by default to 0 (meaning default JDBC driver value)! \nCaught exception: " + getPertinentMessage(e));
+		}
+
+		// Test with any valid async. fetch size:
+		try{
+			ServiceConnection connection = new ConfigurableServiceConnection(asyncFetchSizeProp);
+			assertNotNull(connection.getFetchSize());
+			assertTrue(connection.getFetchSize().length >= 1);
+			assertEquals(connection.getFetchSize()[0], 50);
+		}catch(Exception e){
+			fail("This MUST have succeeded because a valid fetch size has been provided! \nCaught exception: " + getPertinentMessage(e));
+		}
+
 		// Valid user identifier:
 		try{
 			ServiceConnection connection = new ConfigurableServiceConnection(userIdentProp);
diff --git a/test/tap/config/TestConfigurableTAPFactory.java b/test/tap/config/TestConfigurableTAPFactory.java
index e4c8e38..87407bd 100644
--- a/test/tap/config/TestConfigurableTAPFactory.java
+++ b/test/tap/config/TestConfigurableTAPFactory.java
@@ -421,6 +421,11 @@ public class TestConfigurableTAPFactory {
 		public int getNbMaxAsyncJobs(){
 			return -1;
 		}
+
+		@Override
+		public int[] getFetchSize(){
+			return null;
+		}
 	}
 
 }
diff --git a/test/tap/formatter/JSONFormatTest.java b/test/tap/formatter/JSONFormatTest.java
index c7cb393..33deaf6 100644
--- a/test/tap/formatter/JSONFormatTest.java
+++ b/test/tap/formatter/JSONFormatTest.java
@@ -11,9 +11,7 @@ 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;
@@ -23,20 +21,14 @@ 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.log.TAPLog;
 import tap.metadata.TAPColumn;
-import tap.metadata.TAPMetadata;
 import tap.parameters.TAPParameters;
 import testtools.DBTools;
-import uws.service.UserIdentifier;
-import uws.service.file.UWSFileManager;
 import adql.db.DBType;
 import adql.db.DBType.DBDatatype;
-import adql.db.FunctionDef;
 
 /**
  * <p>Test the JSONFormat function {@link JSONFormat#writeResult(TableIterator, OutputStream, TAPExecutionReport, Thread)}.</p>
@@ -56,7 +48,7 @@ public class JSONFormatTest {
 	@BeforeClass
 	public static void setUpBeforeClass() throws Exception{
 		conn = DBTools.createConnection("postgresql", "127.0.0.1", null, "gmantele", "gmantele", "pwd");
-		serviceConn = new ServiceConnectionTest();
+		serviceConn = new ServiceConnection4Test();
 
 		resultingColumns = new TAPColumn[4];
 		resultingColumns[0] = new TAPColumn("ID", new DBType(DBDatatype.VARCHAR));
@@ -147,126 +139,4 @@ public class JSONFormatTest {
 		}
 	}
 
-	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 Collection<String> getGeometries(){
-			return null;
-		}
-
-		@Override
-		public Collection<FunctionDef> getUDFs(){
-			return null;
-		}
-
-		@Override
-		public TAPLog getLogger(){
-			return null;
-		}
-
-		@Override
-		public TAPFactory getFactory(){
-			return null;
-		}
-
-		@Override
-		public UWSFileManager getFileManager(){
-			return null;
-		}
-
-		@Override
-		public Iterator<OutputFormat> getOutputFormats(){
-			return null;
-		}
-
-		@Override
-		public OutputFormat getOutputFormat(String mimeOrAlias){
-			return null;
-		}
-
-		@Override
-		public int getNbMaxAsyncJobs(){
-			return -1;
-		}
-
-		@Override
-		public void setAvailable(boolean isAvailable, String message){}
-
-	}
-
 }
diff --git a/test/tap/formatter/SVFormatTest.java b/test/tap/formatter/SVFormatTest.java
index d523abf..933f9c9 100644
--- a/test/tap/formatter/SVFormatTest.java
+++ b/test/tap/formatter/SVFormatTest.java
@@ -10,9 +10,7 @@ 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;
@@ -20,21 +18,15 @@ 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.log.TAPLog;
 import tap.metadata.TAPColumn;
-import tap.metadata.TAPMetadata;
 import tap.parameters.TAPParameters;
 import testtools.CommandExecute;
 import testtools.DBTools;
-import uws.service.UserIdentifier;
-import uws.service.file.UWSFileManager;
 import adql.db.DBType;
 import adql.db.DBType.DBDatatype;
-import adql.db.FunctionDef;
 
 /**
  * <p>Test the SVFormat function {@link SVFormat#writeResult(TableIterator, OutputStream, TAPExecutionReport, Thread)}.</p>
@@ -54,7 +46,7 @@ public class SVFormatTest {
 	@BeforeClass
 	public static void setUpBeforeClass() throws Exception{
 		conn = DBTools.createConnection("postgresql", "127.0.0.1", null, "gmantele", "gmantele", "pwd");
-		serviceConn = new ServiceConnectionTest();
+		serviceConn = new ServiceConnection4Test();
 
 		resultingColumns = new TAPColumn[4];
 		resultingColumns[0] = new TAPColumn("ID", new DBType(DBDatatype.VARCHAR));
@@ -141,126 +133,4 @@ public class SVFormatTest {
 		}
 	}
 
-	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 Collection<String> getGeometries(){
-			return null;
-		}
-
-		@Override
-		public Collection<FunctionDef> getUDFs(){
-			return null;
-		}
-
-		@Override
-		public TAPLog getLogger(){
-			return null;
-		}
-
-		@Override
-		public TAPFactory getFactory(){
-			return null;
-		}
-
-		@Override
-		public UWSFileManager getFileManager(){
-			return null;
-		}
-
-		@Override
-		public Iterator<OutputFormat> getOutputFormats(){
-			return null;
-		}
-
-		@Override
-		public OutputFormat getOutputFormat(String mimeOrAlias){
-			return null;
-		}
-
-		@Override
-		public int getNbMaxAsyncJobs(){
-			return -1;
-		}
-
-		@Override
-		public void setAvailable(boolean isAvailable, String message){}
-
-	}
-
 }
diff --git a/test/tap/formatter/ServiceConnection4Test.java b/test/tap/formatter/ServiceConnection4Test.java
new file mode 100644
index 0000000..074448a
--- /dev/null
+++ b/test/tap/formatter/ServiceConnection4Test.java
@@ -0,0 +1,139 @@
+package tap.formatter;
+
+import java.util.Collection;
+import java.util.Iterator;
+
+import tap.ServiceConnection;
+import tap.TAPFactory;
+import tap.log.TAPLog;
+import tap.metadata.TAPMetadata;
+import uws.service.UserIdentifier;
+import uws.service.file.UWSFileManager;
+import adql.db.FunctionDef;
+
+public class ServiceConnection4Test 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 Collection<String> getGeometries(){
+		return null;
+	}
+
+	@Override
+	public Collection<FunctionDef> getUDFs(){
+		return null;
+	}
+
+	@Override
+	public TAPLog getLogger(){
+		return null;
+	}
+
+	@Override
+	public TAPFactory getFactory(){
+		return null;
+	}
+
+	@Override
+	public UWSFileManager getFileManager(){
+		return null;
+	}
+
+	@Override
+	public Iterator<OutputFormat> getOutputFormats(){
+		return null;
+	}
+
+	@Override
+	public OutputFormat getOutputFormat(String mimeOrAlias){
+		return null;
+	}
+
+	@Override
+	public int getNbMaxAsyncJobs(){
+		return -1;
+	}
+
+	@Override
+	public void setAvailable(boolean isAvailable, String message){}
+
+	@Override
+	public int[] getFetchSize(){
+		return null;
+	}
+
+}
\ No newline at end of file
diff --git a/test/tap/formatter/TextFormatTest.java b/test/tap/formatter/TextFormatTest.java
index d6632da..dfb266e 100644
--- a/test/tap/formatter/TextFormatTest.java
+++ b/test/tap/formatter/TextFormatTest.java
@@ -10,9 +10,7 @@ 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;
@@ -20,21 +18,15 @@ 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.log.TAPLog;
 import tap.metadata.TAPColumn;
-import tap.metadata.TAPMetadata;
 import tap.parameters.TAPParameters;
 import testtools.CommandExecute;
 import testtools.DBTools;
-import uws.service.UserIdentifier;
-import uws.service.file.UWSFileManager;
 import adql.db.DBType;
 import adql.db.DBType.DBDatatype;
-import adql.db.FunctionDef;
 
 /**
  * <p>Test the TestFormat function {@link TestFormat#writeResult(TableIterator, OutputStream, TAPExecutionReport, Thread)}.</p>
@@ -54,7 +46,7 @@ public class TextFormatTest {
 	@BeforeClass
 	public static void setUpBeforeClass() throws Exception{
 		conn = DBTools.createConnection("postgresql", "127.0.0.1", null, "gmantele", "gmantele", "pwd");
-		serviceConn = new ServiceConnectionTest();
+		serviceConn = new ServiceConnection4Test();
 
 		resultingColumns = new TAPColumn[4];
 		resultingColumns[0] = new TAPColumn("ID", new DBType(DBDatatype.VARCHAR));
@@ -141,126 +133,4 @@ public class TextFormatTest {
 		}
 	}
 
-	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 Collection<String> getGeometries(){
-			return null;
-		}
-
-		@Override
-		public Collection<FunctionDef> getUDFs(){
-			return null;
-		}
-
-		@Override
-		public TAPLog getLogger(){
-			return null;
-		}
-
-		@Override
-		public TAPFactory getFactory(){
-			return null;
-		}
-
-		@Override
-		public UWSFileManager getFileManager(){
-			return null;
-		}
-
-		@Override
-		public Iterator<OutputFormat> getOutputFormats(){
-			return null;
-		}
-
-		@Override
-		public OutputFormat getOutputFormat(String mimeOrAlias){
-			return null;
-		}
-
-		@Override
-		public int getNbMaxAsyncJobs(){
-			return -1;
-		}
-
-		@Override
-		public void setAvailable(boolean isAvailable, String message){}
-
-	}
-
 }
diff --git a/test/tap/formatter/VOTableFormatTest.java b/test/tap/formatter/VOTableFormatTest.java
index d55200a..eb28fde 100644
--- a/test/tap/formatter/VOTableFormatTest.java
+++ b/test/tap/formatter/VOTableFormatTest.java
@@ -10,9 +10,7 @@ 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;
@@ -20,22 +18,16 @@ 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.log.TAPLog;
 import tap.metadata.TAPColumn;
-import tap.metadata.TAPMetadata;
 import tap.parameters.TAPParameters;
 import testtools.CommandExecute;
 import testtools.DBTools;
 import uk.ac.starlink.votable.DataFormat;
-import uws.service.UserIdentifier;
-import uws.service.file.UWSFileManager;
 import adql.db.DBType;
 import adql.db.DBType.DBDatatype;
-import adql.db.FunctionDef;
 
 /**
  * <p>Test the VOTableFormat function {@link VOTableFormat#writeResult(TableIterator, OutputStream, TAPExecutionReport, Thread)}.</p>
@@ -55,7 +47,7 @@ public class VOTableFormatTest {
 	@BeforeClass
 	public static void setUpBeforeClass() throws Exception{
 		conn = DBTools.createConnection("postgresql", "127.0.0.1", null, "gmantele", "gmantele", "pwd");
-		serviceConn = new ServiceConnectionTest();
+		serviceConn = new ServiceConnection4Test();
 
 		resultingColumns = new TAPColumn[4];
 		resultingColumns[0] = new TAPColumn("ID", new DBType(DBDatatype.VARCHAR));
@@ -146,126 +138,4 @@ public class VOTableFormatTest {
 		}
 	}
 
-	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 Collection<String> getGeometries(){
-			return null;
-		}
-
-		@Override
-		public Collection<FunctionDef> getUDFs(){
-			return null;
-		}
-
-		@Override
-		public TAPLog getLogger(){
-			return null;
-		}
-
-		@Override
-		public TAPFactory getFactory(){
-			return null;
-		}
-
-		@Override
-		public UWSFileManager getFileManager(){
-			return null;
-		}
-
-		@Override
-		public Iterator<OutputFormat> getOutputFormats(){
-			return null;
-		}
-
-		@Override
-		public OutputFormat getOutputFormat(String mimeOrAlias){
-			return null;
-		}
-
-		@Override
-		public int getNbMaxAsyncJobs(){
-			return -1;
-		}
-
-		@Override
-		public void setAvailable(boolean isAvailable, String message){}
-
-	}
-
 }
diff --git a/test/tap/parameters/ServiceConnectionOfTest.java b/test/tap/parameters/ServiceConnectionOfTest.java
index cb0d793..029c056 100644
--- a/test/tap/parameters/ServiceConnectionOfTest.java
+++ b/test/tap/parameters/ServiceConnectionOfTest.java
@@ -169,4 +169,9 @@ public class ServiceConnectionOfTest implements ServiceConnection {
 		return null;
 	}
 
+	@Override
+	public int[] getFetchSize(){
+		return null;
+	}
+
 }
\ No newline at end of file
-- 
GitLab