Repository: pgjdbc/pgjdbc
Pull request #1707 (2020-02-14 14:21:48)

feat: implementation of adaptive fetching
Implementation of adaptive fetching logic during process of fetching result set.
Change adds three new properties used with adaptive fetching:
- adaptiveFetch - use to enable/disable work of adaptive fetching
- adaptiveFetchMinimum - use to define minimum size computed during adaptive fetching
- adaptiveFetchmaximum - use to define maximum size computed during adaptive fetching
Adaptive fetching is used to compute fetch size to fully use size defined by maxResultBuffer.
Computing is made by dividing maxResultBuffer size by max row result size noticed so far.
Each query have seperate adaptive fetch size computed, but same queries have it shared.
If adaptive fetch is turned on, first fetch is going to be made with defaultRowFetchSize,
next fetching of resultSet will be made with computed adaptive fetch size.
If adaptive fetch is turned on during fetching, then first fetching made by ResultSet will
be made with defaultRowFetchSize, next will use computed adaptive fetch size.
Additionally, resultSet have new method - getLastUsedFetchSize to get fetch size used during
last fetching. If adaptive fetching is turned on, then getFetchSize will return lastUsedFetchSize.
Property adaptiveFetch need properties defaultRowFetchSize and maxResultBuffer to work.

Commit made for Heimdall Data's request to implement adaptive fetching.

### All Submissions:

* [x] Have you followed the guidelines in our [Contributing](https://github.com/pgjdbc/pgjdbc/blob/master/CONTRIBUTING.md) document?
* [ ] Have you checked to ensure there aren't other open [Pull Requests](../../pulls) for the same update/change? (Because I don't think there is pull request for this change.)

<!-- You can erase any parts of this template not applicable to your Pull Request. -->

### New Feature Submissions:

1. [x] Does your submission pass tests?
2. [x] Does mvn checkstyle:check pass ?

Tests for adaptive fetching aren't added, because I wasn't sure where in tests I could implement new class with tests for this (if required I can add tests, but would be really glad for information in which test package I could implement them). 

The motivation behind this feature is to make fetching more effective by using full memory capacity defined by maxResultBuffer. Adaptive fetching is going to compute size to maximize the number of rows during next fetching, but also predicting it to be safe and not exceed maxResultBuffer size (prediction is made by finding max result row size noticed so far and using it to compute adaptive fetch size for query). 

If somebody would want to test how it works, then I can suggest below scenario:
Connection settings:
- maxResultBuffer set really low i.e. 200 bytes
- defaultRowFetchSize set as 4 
- adaptiveFetch set true. 
Database setting:
- create table with one column containing string with 10 characters and add some rows
What should happen during fetching:
- first fetch would be done with size 4
- next fetching of data would be done with size 20 (because 200/10 = 20)

I would be glad for any comments about possibilities to improve my code, because I know that I changed a lot and I may missed more effective or smarter coding solutions.

davecramer: https://github.com/pgjdbc/pgjdbc/blob/master/docs/documentation/head/connect.md will also have to reflect the new properties
adrklos: @davecramer I committed before some about new properties in https://github.com/adrklos/pgjdbc/blob/heim_adaptive_fetch/docs/documentation/head/connect.md, and now I added extra information about what benefit gives using of adaptive fetching. Now I have no idea what more I could add, so if actual information is not enough, then please specify what information should be added for properties.


davecramer: @adrklos apologies. I must have missed it the first time around.
davecramer: We already have a query cache and a way to calculate the key. Have you looked at the code in https://github.com/pgjdbc/pgjdbc/blob/23cce8ad35d9af6e2a1cb97fac69fdc0a7f94b42/pgjdbc/src/main/java/org/postgresql/core/QueryExecutorBase.java#L309 ?

I'd also like to explore re-using the existing query cache if possible?
adrklos: No, I haven't checked that code. And I would want to explain one thing, just to be clear: there is no implemented caching of computed size for queries after reading all of data rows, kept values in AdaptiveFetchQueryMonitoring in theory should exist only to a moment, when last query (last query in mean query which at the moment isn't identical to other called queries) reads Command completion message. After that, computed value should be removed to solve problem if the biggest data rows would be removed from result set before next reading with fetching.
davecramer: Even so you may want to look at how the CacheQueryKey is generated.
adrklos: Ok, I looked at generating key and at cache work, and I'm not really sure what to do with this. For needs of adaptive fetch I think key as sql string is enough. It may be used with existing cache, information from AdaptiveFetchQueryInfo could be put in CachedQuery class, methods of AdaptiveFetchQueryMonitoring may be moved to QueryExecutorBase. The only things, which may be problematic, are removing objects from cache after use and doubling information for one query (because of different keys, one hard-coded string key, and second generated by createQueryKey). On other hand if adaptive fetch objects would use sql string as key, then it would solve problem with removing, because it's used only for sql without columns returned and parametrized, so the ones which I think aren't used for fetching (but I'm not sure).
davecramer: I'm concerned that there is a lot of code here and no tests. I'd like to see some unit tests as well as an integration test where we attempt to read a large result set and see that adaptive fetching actually does what we think it does.
sehrope: > I'm concerned that there is a lot of code here and no tests. I'd like to see some unit tests as well as an integration test where we attempt to read a large result set and see that adaptive fetching actually does what we think it does.

+1 I took a peek at the PR. Not an actual review, just skimming through to get an idea of what it's trying to do and agree it's a lot of code change.
adrklos: @davecramer Ok, I understand your concerns about this situation. I will prepare unit tests for AdaptiveFetchQueryMonitoring class (as I think it's a class which needs unit tests the most) and integration test for adaptive fetching process.
codecov-io: # [Codecov](https://codecov.io/gh/pgjdbc/pgjdbc/pull/1707?src=pr&el=h1) Report
> Merging [#1707](https://codecov.io/gh/pgjdbc/pgjdbc/pull/1707?src=pr&el=desc) into [master](https://codecov.io/gh/pgjdbc/pgjdbc/commit/cc31c13f7810e171c54729a29f494d69b7f1550a?src=pr&el=desc) will **increase** coverage by `3.75%`.
> The diff coverage is `75.39%`.



```diff
@@             Coverage Diff              @@
##             master    #1707      +/-   ##
============================================
+ Coverage     65.47%   69.23%   +3.75%     
- Complexity     3936     4239     +303     
============================================
  Files           186      189       +3     
  Lines         17115    17443     +328     
  Branches       2810     2887      +77     
============================================
+ Hits          11206    12076     +870     
+ Misses         4642     4068     -574     
- Partials       1267     1299      +32
```

adrklos: @davecramer I added some unit tests (all of them for AdaptiveFetchQueryMonitoring class, as I mentioned before) and some integration tests for adaptive fetching process. I would be glad for any information about possibilities to improve my code or ideas for new tests, which I might have forgotten to add.
davecramer: @adrklos thanks I saw this. The only thing I'd like to see and I'm not exactly sure where to put this is an explanation like in the commit log put in the code so that people reading this in the future won't have to look in the commit log for how it works. Perhaps in pgjdbc/src/main/java/org/postgresql/core/v3/adaptivefetch/AdaptiveFetchQueryMonitoring.java ?
adrklos: @davecramer Yes, adding it as explanation for class pgjdbc/src/main/java/org/postgresql/core/v3/adaptivefetch/AdaptiveFetchQueryMonitoring.java should be the best. Adaptive fetch process implementation required changes in several places, however analysing the code flow would lead to AdaptiveFetchQueryMonitoring class, so I think putting explanation there will be most appropriate.
adrklos: @davecramer I added explanation, the same as is in the commit (I hope it would be enough to understand idea behind adaptive fetching process). I see that `testDuringSendBigTransactionConnectionCloseSlotStatusNotActive` test failed, and as I remember this test is flaky, so I hope it doesn't need my reaction to solve this problem.
davecramer: Excellent. Thanks. I restarted that job

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1aead140..740262d1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -112,6 +112,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
 ### Changed
 - `ssl=true` implies `sslmode=verify-full`, that is it requires valid server certificate [cdeeaca4](https://github.com/pgjdbc/pgjdbc/commit/cdeeaca47dc3bc6f727c79a582c9e4123099526e)
 
+targetServerType=master has been deprecated in favour of targetServerType=primary. master
+will still be accepted but not documented.
 ### Added
 - Support for `sslmode=allow/prefer/require` [cdeeaca4](https://github.com/pgjdbc/pgjdbc/commit/cdeeaca47dc3bc6f727c79a582c9e4123099526e)
 
diff --git a/README.md b/README.md
index 5f8e7bb4..6901d213 100644
--- a/README.md
+++ b/README.md
@@ -153,6 +153,9 @@ In addition to the standard connection parameters the driver supports a number o
 | reWriteBatchedInserts         | Boolean | false  | Enable optimization to rewrite and collapse compatible INSERT statements that are batched. |
 | escapeSyntaxCallMode          | String  | select   | Specifies how JDBC escape call syntax is transformed into underlying SQL (CALL/SELECT), for invoking procedures or functions (requires server version >= 11), possible values: select, callIfNoReturn, call |
 | maxResultBuffer               | String  | null    | Specifies size of result buffer in bytes, which can't be exceeded during reading result set. Can be specified as particular size (i.e. "100", "200M" "2G") or as percent of max heap memory (i.e. "10p", "20pct", "50percent") |
+| adaptiveFetch                 | Boolean | false   | Specifies if number of rows fetched in ResultSet by each fetch iteration should be dynamic. Number of rows will be calculated by dividing maxResultBuffer size into max row size observed so far. Requires declaring maxResultBuffer and defaultRowFetchSize for first iteration. 
+| adaptiveFetchMinimum          | Integer | 0       | Specifies minimum number of rows, which can be calculated by adaptiveFetch. Number of rows used by adaptiveFetch cannot go below this value. 
+| adaptiveFetchMaximum          | Integer | -1      | Specifies maximum number of rows, which can be calculated by adaptiveFetch. Number of rows used by adaptiveFetch cannot go above this value. Any negative number set as adaptiveFetchMaximum is used by adaptiveFetch as infinity number of rows.
 
 ## Contributing
 For information on how to contribute to the project see the [Contributing Guidelines](CONTRIBUTING.md)
diff --git a/docs/documentation/head/connect.md b/docs/documentation/head/connect.md
index 5997ee77..9217319a 100644
--- a/docs/documentation/head/connect.md
+++ b/docs/documentation/head/connect.md
@@ -441,10 +441,12 @@ Connection conn = DriverManager.getConnection(url);
 * **targetServerType** = String
 
 	Allows opening connections to only servers with required state, 
-	the allowed values are any, master, slave, secondary, preferSlave and preferSecondary. 
-	The master/slave distinction is currently done by observing if the server allows writes. 
+	the allowed values are any, primary, master, slave, secondary, preferSlave and preferSecondary. 
+	The primary/secondary distinction is currently done by observing if the server allows writes. 
 	The value preferSecondary tries to connect to secondary if any are available, 
-	otherwise allows falls back to connecting also to master.
+	otherwise allows falls back to connecting also to primary.
+	- *N.B.* the words master and slave are being deprecated. We will silently accept them, but primary
+	and secondary are encouraged.
 
 * **hostRecheckSeconds** = int
 
@@ -506,6 +508,31 @@ Connection conn = DriverManager.getConnection(url);
     
 	By default, maxResultBuffer is not set (is null), what means that reading of results gonna be performed without limits.
 	
+* **adaptiveFetch** = boolean	
+
+    Specifies if number of rows, fetched in `ResultSet` by one fetch with trip to the database, should be dynamic.
+    Using dynamic number of rows, computed by adaptive fetch, allows to use most of the buffer declared in `maxResultBuffer` property.
+    Number of rows would be calculated by dividing `maxResultBuffer` size into max row size observed so far, rounded down.
+    First fetch will have number of rows declared in `defaultRowFetchSize`.
+    Number of rows can be limited by `adaptiveFetchMinimum` and `adaptiveFetchMaximum`. 
+    Requires declaring of `maxResultBuffer` and `defaultRowFetchSize` to work.	
+    
+    By default, adaptiveFetch is false.
+    
+* **adaptiveFetchMinimum** = int
+
+    Specifies the lowest number of rows which can be calculated by `adaptiveFetch`.
+    Requires `adaptiveFetch` set to true to work.
+    
+    By default, minimum of rows calculated by `adaptiveFetch` is 0. 
+
+* **adaptiveFetchMaximum** = int
+
+	Specifies the highest number of rows which can be calculated by `adaptiveFetch`.
+    Requires `adaptiveFetch` set to true to work.
+        
+    By default, maximum of rows calculated by `adaptiveFetch` is -1, which is understood as infinite.
+        
 <a name="unix sockets"></a>
 ## Unix sockets
 
@@ -549,14 +576,20 @@ postgres installation that has identical data on each node.
 For example streaming replication postgres or postgres-xc cluster.
 
 For example an application can create two connection pools. 
-One data source is for writes, another for reads. The write pool limits connections only to master node:
+One data source is for writes, another for reads. The write pool limits connections only to a primary node:
 
-`jdbc:postgresql://node1,node2,node3/accounting?targetServerType=master`.
+`jdbc:postgresql://node1,node2,node3/accounting?targetServerType=primary`.
 
-And read pool balances connections between slaves nodes, but allows connections also to master if no slaves are available:
+And read pool balances connections between secondary nodes, but allows connections also to a primary if no secondaries are available:
 
-`jdbc:postgresql://node1,node2,node3/accounting?targetServerType=preferSlave&loadBalanceHosts=true`
+`jdbc:postgresql://node1,node2,node3/accounting?targetServerType=preferSecondary&loadBalanceHosts=true`
 
+<<<<<<< HEAD
 If a slave fails, all slaves in the list will be tried first. If the case that there are no available slaves
 the master will be tried. If all of the servers are marked as "can't connect" in the cache then an attempt
 will be made to connect to all of the hosts in the URL in order.
+=======
+If a secondary fails, all secondaries in the list will be tried first. If the case that there are no available secondaries
+the primary will be tried. If all of the servers are marked as "can't connect" in the cache then an attempt
+will be made to connect to all of the hosts in the URL in order.
+>>>>>>> 263d23605b9e4900fc161da165829a6b2ae168fc
diff --git a/docs/documentation/head/ext.md b/docs/documentation/head/ext.md
index 2a46d469..141e287b 100644
--- a/docs/documentation/head/ext.md
+++ b/docs/documentation/head/ext.md
@@ -30,7 +30,7 @@ actually use these extensions to implement Large Objects, etc.
 # Accessing the Extensions
 
 To access some of the extensions, you need to use some extra methods in the
-`org.postgresql.PGConnection` class. In this case, you would need to case the
+`org.postgresql.PGConnection` class. In this case, you would need to cast the
 return value of `Driver.getConnection()`. For example:
 
 ```java
diff --git a/pgjdbc/src/main/java/org/postgresql/PGConnection.java b/pgjdbc/src/main/java/org/postgresql/PGConnection.java
index 6f700c52..4c75bdc3 100644
--- a/pgjdbc/src/main/java/org/postgresql/PGConnection.java
+++ b/pgjdbc/src/main/java/org/postgresql/PGConnection.java
@@ -313,4 +313,19 @@ public interface PGConnection {
    * @since 42.2.6
    */
   String getParameterStatus(String parameterName);
+
+  /**
+   * Method to turn on/off adaptive fetch for connection. Existing statements and resultSets won't
+   * be affected by change here.
+   *
+   * @param adaptiveFetch desired state of adaptive fetch.
+   */
+  void setAdaptiveFetch(boolean adaptiveFetch);
+
+  /**
+   * Method to get state of adaptive fetch for connection.
+   *
+   * @return state of adaptive fetch (turned on or off)
+   */
+  boolean getAdaptiveFetch();
 }
diff --git a/pgjdbc/src/main/java/org/postgresql/PGProperty.java b/pgjdbc/src/main/java/org/postgresql/PGProperty.java
index 2874f200..ef42729e 100644
--- a/pgjdbc/src/main/java/org/postgresql/PGProperty.java
+++ b/pgjdbc/src/main/java/org/postgresql/PGProperty.java
@@ -22,6 +22,37 @@ import java.util.Properties;
  */
 public enum PGProperty {
 
+  /**
+   * Specifies if number of rows fetched in ResultSet by one fetch with trip to the database should
+   * be dynamic. Number of rows would be calculated by dividing maxResultBuffer size into max row
+   * size observed so far, rounded down. First fetch will have number of rows declared in
+   * defaultRowFetchSize. Number of rows can be limited by adaptiveFetchMinimum and
+   * adaptiveFetchMaximum. Requires declaring of maxResultBuffer and defaultRowFetchSize to work.
+   * Default value is false.
+   */
+  ADAPTIVE_FETCH(
+    "adaptiveFetch",
+    "false",
+    "Specifies if number of rows fetched in ResultSet should be adaptive to maxResultBuffer and max row size."),
+
+  /**
+   * Specifies the highest number of rows which can be calculated by adaptiveFetch. Requires
+   * adaptiveFetch set to true to work. Default value is -1 (used as infinity).
+   */
+  ADAPTIVE_FETCH_MAXIMUM(
+    "adaptiveFetchMaximum",
+    "-1",
+    "Specifies minimum number of rows used by adaptive fetch."),
+
+  /**
+   * Specifies the lowest number of rows which can be calculated by adaptiveFetch. Requires
+   * adaptiveFetch set to true to work. Default value is 0.
+   */
+  ADAPTIVE_FETCH_MINIMUM(
+    "adaptiveFetchMinimum",
+    "0",
+    "Specifies maximum number of rows used by adaptive fetch."),
+
   /**
    * When using the V3 protocol the driver monitors changes in certain server configuration
    * parameters that should not be touched by end users. The {@code client_encoding} setting is set
@@ -583,6 +614,7 @@ public enum PGProperty {
     null,
     "The password for the client's ssl key (ignored if sslpasswordcallback is set)"),
 
+
   /**
    * The classname instantiating {@code javax.security.auth.callback.CallbackHandler} to use.
    */
@@ -626,7 +658,7 @@ public enum PGProperty {
     "any",
     "Specifies what kind of server to connect",
     false,
-    new String[] {"any", "master", "slave", "secondary",  "preferSlave", "preferSecondary"}),
+    new String [] {"any", "primary", "master", "slave", "secondary",  "preferSlave", "preferSecondary"}),
 
   /**
    * Enable or disable TCP keep-alive. The default is {@code false}.
diff --git a/pgjdbc/src/main/java/org/postgresql/PGStatement.java b/pgjdbc/src/main/java/org/postgresql/PGStatement.java
index cbe3b2e8..1c2114d2 100644
--- a/pgjdbc/src/main/java/org/postgresql/PGStatement.java
+++ b/pgjdbc/src/main/java/org/postgresql/PGStatement.java
@@ -79,4 +79,19 @@ public interface PGStatement {
    * @since build 302
    */
   int getPrepareThreshold();
+
+  /**
+   * Method to turn on/off adaptive fetch for statement. Existing resultSets won't be affected by
+   * change here.
+   *
+   * @param adaptiveFetch desired state of adaptive fetch.
+   */
+  void setAdaptiveFetch(boolean adaptiveFetch);
+
+  /**
+   * Method to get state of adaptive fetch for statement.
+   *
+   * @return state of adaptive fetch (turned on or off)
+   */
+  boolean getAdaptiveFetch();
 }
diff --git a/pgjdbc/src/main/java/org/postgresql/core/PGStream.java b/pgjdbc/src/main/java/org/postgresql/core/PGStream.java
index 1637bfae..b7759872 100644
--- a/pgjdbc/src/main/java/org/postgresql/core/PGStream.java
+++ b/pgjdbc/src/main/java/org/postgresql/core/PGStream.java
@@ -58,6 +58,8 @@ public class PGStream implements Closeable, Flushable {
   private long maxResultBuffer = -1;
   private long resultBufferByteCount = 0;
 
+  private int maxRowSize = -1;
+
   /**
    * Constructor: Connect to the PostgreSQL back end and return a stream connection.
    *
@@ -477,6 +479,8 @@ public class PGStream implements Closeable, Flushable {
     int nf = receiveInteger2();
     //size = messageSize - 4 bytes of message size - 2 bytes of field count - 4 bytes for each column length
     int dataToReadSize = messageSize - 4 - 2 - 4 * nf;
+    setMaxRowSize(dataToReadSize);
+
     byte[][] answer = new byte[nf][];
 
     increaseByteCounter(dataToReadSize);
@@ -643,6 +647,46 @@ public class PGStream implements Closeable, Flushable {
     maxResultBuffer = PGPropertyMaxResultBufferParser.parseProperty(value);
   }
 
+  /**
+   * Method to get MaxResultBuffer from PGStream.
+   *
+   * @return size of MaxResultBuffer
+   */
+  public long getMaxResultBuffer() {
+    return maxResultBuffer;
+  }
+
+  /**
+   * The idea behind this method is to keep in maxRowSize the size of biggest read data row. As
+   * there may be many data rows send after each other for a query, then value in maxRowSize would
+   * contain value noticed so far, because next data rows and their sizes are not read for that
+   * moment. We want it increasing, because the size of the biggest among data rows will be used
+   * during computing new adaptive fetch size for the query.
+   *
+   * @param rowSize new value to be set as maxRowSize
+   */
+  public void setMaxRowSize(int rowSize) {
+    if (rowSize > maxRowSize) {
+      maxRowSize = rowSize;
+    }
+  }
+
+  /**
+   * Method to get actual max row size noticed so far.
+   *
+   * @return value of max row size
+   */
+  public int getMaxRowSize() {
+    return maxRowSize;
+  }
+
+  /**
+   * Method to clear value of max row size noticed so far.
+   */
+  public void clearMaxRowSize() {
+    maxRowSize = -1;
+  }
+
   /**
    * Method to clear count of byte buffer.
    */
diff --git a/pgjdbc/src/main/java/org/postgresql/core/QueryExecutor.java b/pgjdbc/src/main/java/org/postgresql/core/QueryExecutor.java
index b5c97244..d8958b1e 100644
--- a/pgjdbc/src/main/java/org/postgresql/core/QueryExecutor.java
+++ b/pgjdbc/src/main/java/org/postgresql/core/QueryExecutor.java
@@ -145,6 +145,25 @@ public interface QueryExecutor extends TypeTransferModeRegistry {
   void execute(Query query, ParameterList parameters, ResultHandler handler, int maxRows,
       int fetchSize, int flags) throws SQLException;
 
+  /**
+   * Execute a Query with adaptive fetch, passing results to a provided ResultHandler.
+   *
+   * @param query the query to execute; must be a query returned from calling
+   *        {@link #wrap(List)} on this QueryExecutor object.
+   * @param parameters the parameters for the query. Must be non-<code>null</code> if the query
+   *        takes parameters. Must be a parameter object returned by
+   *        {@link org.postgresql.core.Query#createParameterList()}.
+   * @param handler a ResultHandler responsible for handling results generated by this query
+   * @param maxRows the maximum number of rows to retrieve
+   * @param fetchSize if QUERY_FORWARD_CURSOR is set, the preferred number of rows to retrieve
+   *        before suspending
+   * @param flags a combination of QUERY_* flags indicating how to handle the query.
+   * @param adaptiveFetch state of adaptiveFetch to use during execution
+   * @throws SQLException if query execution fails
+   */
+  void execute(Query query, ParameterList parameters, ResultHandler handler, int maxRows,
+      int fetchSize, int flags, boolean adaptiveFetch) throws SQLException;
+
   /**
    * Execute several Query, passing results to a provided ResultHandler.
    *
@@ -165,15 +184,37 @@ public interface QueryExecutor extends TypeTransferModeRegistry {
   void execute(Query[] queries, ParameterList[] parameterLists, BatchResultHandler handler, int maxRows,
       int fetchSize, int flags) throws SQLException;
 
+  /**
+   * Execute several Query with adaptive fetch, passing results to a provided ResultHandler.
+   *
+   * @param queries the queries to execute; each must be a query returned from calling
+   *        {@link #wrap(List)} on this QueryExecutor object.
+   * @param parameterLists the parameter lists for the queries. The parameter lists correspond 1:1
+   *        to the queries passed in the <code>queries</code> array. Each must be non-
+   *        <code>null</code> if the corresponding query takes parameters, and must be a parameter
+   *        object returned by {@link org.postgresql.core.Query#createParameterList()} created by
+   *        the corresponding query.
+   * @param handler a ResultHandler responsible for handling results generated by this query
+   * @param maxRows the maximum number of rows to retrieve
+   * @param fetchSize if QUERY_FORWARD_CURSOR is set, the preferred number of rows to retrieve
+   *        before suspending
+   * @param flags a combination of QUERY_* flags indicating how to handle the query.
+   * @param adaptiveFetch state of adaptiveFetch to use during execution
+   * @throws SQLException if query execution fails
+   */
+  void execute(Query[] queries, ParameterList[] parameterLists, BatchResultHandler handler, int maxRows,
+      int fetchSize, int flags, boolean adaptiveFetch) throws SQLException;
+
   /**
    * Fetch additional rows from a cursor.
    *
    * @param cursor the cursor to fetch from
    * @param handler the handler to feed results to
    * @param fetchSize the preferred number of rows to retrieve before suspending
+   * @param adaptiveFetch state of adaptiveFetch to use during fetching
    * @throws SQLException if query execution fails
    */
-  void fetch(ResultCursor cursor, ResultHandler handler, int fetchSize) throws SQLException;
+  void fetch(ResultCursor cursor, ResultHandler handler, int fetchSize, boolean adaptiveFetch) throws SQLException;
 
   /**
    * Create an unparameterized Query object suitable for execution by this QueryExecutor. The
@@ -466,4 +507,46 @@ public interface QueryExecutor extends TypeTransferModeRegistry {
   Map<String,String> getParameterStatuses();
 
   String getParameterStatus(String parameterName);
+
+  /**
+   * Method to get fetch size computed by adaptive fetch size for given query.
+   *
+   * @param adaptiveFetch state of adaptive fetch, which should be used during retrieving
+   * @param cursor        Cursor used by resultSet, containing query, have to be able to cast to
+   *                      Portal class.
+   * @return fetch size computed by adaptive fetch size for given query passed inside cursor
+   */
+  int getAdaptiveFetchSize(boolean adaptiveFetch, ResultCursor cursor);
+
+  /**
+   * Method to get state of adaptive fetch inside QueryExecutor.
+   *
+   * @return state of adaptive fetch inside QueryExecutor
+   */
+  boolean getAdaptiveFetch();
+
+  /**
+   * Method to set state of adaptive fetch inside QueryExecutor.
+   *
+   * @param adaptiveFetch desired state of adaptive fetch
+   */
+  void setAdaptiveFetch(boolean adaptiveFetch);
+
+  /**
+   * Method to add query to adaptive fetch monitoring inside QueryExecutor.
+   *
+   * @param adaptiveFetch state of adaptive fetch used during adding query
+   * @param cursor        Cursor used by resultSet, containing query, have to be able to cast to
+   *                      Portal class.
+   */
+  void addQueryToAdaptiveFetchMonitoring(boolean adaptiveFetch, ResultCursor cursor);
+
+  /**
+   * Method to remove query from adaptive fetch monitoring inside QueryExecutor
+   *
+   * @param adaptiveFetch state of adaptive fetch used during removing query
+   * @param cursor        Cursor used by resultSet, containing query, have to be able to cast to
+   *                      Portal class.
+   */
+  void removeQueryFromAdaptiveFetchMonitoring(boolean adaptiveFetch, ResultCursor cursor);
 }
diff --git a/pgjdbc/src/main/java/org/postgresql/core/v3/ConnectionFactoryImpl.java b/pgjdbc/src/main/java/org/postgresql/core/v3/ConnectionFactoryImpl.java
index aec04473..9737a82e 100644
--- a/pgjdbc/src/main/java/org/postgresql/core/v3/ConnectionFactoryImpl.java
+++ b/pgjdbc/src/main/java/org/postgresql/core/v3/ConnectionFactoryImpl.java
@@ -255,10 +255,10 @@ public class ConnectionFactoryImpl extends ConnectionFactory {
         QueryExecutor queryExecutor = new QueryExecutorImpl(newStream, user, database,
             cancelSignalTimeout, info);
 
-        // Check Master or Secondary
+        // Check Primary or Secondary
         HostStatus hostStatus = HostStatus.ConnectOK;
         if (candidateHost.targetServerType != HostRequirement.any) {
-          hostStatus = isMaster(queryExecutor) ? HostStatus.Master : HostStatus.Secondary;
+          hostStatus = isPrimary(queryExecutor) ? HostStatus.Primary : HostStatus.Secondary;
         }
         GlobalHostStatusTracker.reportHostStatus(hostSpec, hostStatus);
         knownStates.put(hostSpec, hostStatus);
@@ -753,7 +753,7 @@ public class ConnectionFactoryImpl extends ConnectionFactory {
 
   }
 
-  private boolean isMaster(QueryExecutor queryExecutor) throws SQLException, IOException {
+  private boolean isPrimary(QueryExecutor queryExecutor) throws SQLException, IOException {
     Tuple results = SetupQueryRunner.run(queryExecutor, "show transaction_read_only", true);
     String value = queryExecutor.getEncoding().decode(results.get(0));
     return value.equalsIgnoreCase("off");
diff --git a/pgjdbc/src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java b/pgjdbc/src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java
index ab06a88b..4fd7c466 100644
--- a/pgjdbc/src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java
+++ b/pgjdbc/src/main/java/org/postgresql/core/v3/QueryExecutorImpl.java
@@ -33,6 +33,7 @@ import org.postgresql.core.SqlCommandType;
 import org.postgresql.core.TransactionState;
 import org.postgresql.core.Tuple;
 import org.postgresql.core.Utils;
+import org.postgresql.core.v3.adaptivefetch.AdaptiveFetchQueryMonitoring;
 import org.postgresql.core.v3.replication.V3ReplicationProtocol;
 import org.postgresql.jdbc.AutoSave;
 import org.postgresql.jdbc.BatchResultHandler;
@@ -128,10 +129,15 @@ public class QueryExecutorImpl extends QueryExecutorBase {
    */
   private final CommandCompleteParser commandCompleteParser = new CommandCompleteParser();
 
+  private final AdaptiveFetchQueryMonitoring adaptiveFetchQueryMonitoring;
+
   public QueryExecutorImpl(PGStream pgStream, String user, String database,
       int cancelSignalTimeout, Properties info) throws SQLException, IOException {
     super(pgStream, user, database, cancelSignalTimeout, info);
 
+    long maxResultBuffer = pgStream.getMaxResultBuffer();
+    this.adaptiveFetchQueryMonitoring = new AdaptiveFetchQueryMonitoring(maxResultBuffer, info);
+
     this.allowEncodingChanges = PGProperty.ALLOW_ENCODING_CHANGES.getBoolean(info);
     this.cleanupSavePoints = PGProperty.CLEANUP_SAVEPOINTS.getBoolean(info);
     this.replicationProtocol = new V3ReplicationProtocol(this, pgStream);
@@ -276,6 +282,11 @@ public class QueryExecutorImpl extends QueryExecutorBase {
 
   public synchronized void execute(Query query, ParameterList parameters, ResultHandler handler,
       int maxRows, int fetchSize, int flags) throws SQLException {
+    execute(query, parameters, handler, maxRows, fetchSize, flags, false);
+  }
+
+  public synchronized void execute(Query query, ParameterList parameters, ResultHandler handler,
+      int maxRows, int fetchSize, int flags, boolean adaptiveFetch) throws SQLException {
     waitOnLock();
     if (LOGGER.isLoggable(Level.FINEST)) {
       LOGGER.log(Level.FINEST, "  simple execute, handler={0}, maxRows={1}, fetchSize={2}, flags={3}",
@@ -303,14 +314,14 @@ public class QueryExecutorImpl extends QueryExecutorBase {
         handler = sendQueryPreamble(handler, flags);
         autosave = sendAutomaticSavepoint(query, flags);
         sendQuery(query, (V3ParameterList) parameters, maxRows, fetchSize, flags,
-            handler, null);
+            handler, null, adaptiveFetch);
         if ((flags & QueryExecutor.QUERY_EXECUTE_AS_SIMPLE) != 0) {
           // Sync message is not required for 'Q' execution as 'Q' ends with ReadyForQuery message
           // on its own
         } else {
           sendSync();
         }
-        processResults(handler, flags);
+        processResults(handler, flags, adaptiveFetch);
         estimatedReceiveBufferBytes = 0;
       } catch (PGBindException se) {
         // There are three causes of this error, an
@@ -328,7 +339,7 @@ public class QueryExecutorImpl extends QueryExecutorBase {
         // transaction in progress?
         //
         sendSync();
-        processResults(handler, flags);
+        processResults(handler, flags, adaptiveFetch);
         estimatedReceiveBufferBytes = 0;
         handler
             .handleError(new PSQLException(GT.tr("Unable to bind parameter values for statement."),
@@ -462,6 +473,12 @@ public class QueryExecutorImpl extends QueryExecutorBase {
 
   public synchronized void execute(Query[] queries, ParameterList[] parameterLists,
       BatchResultHandler batchHandler, int maxRows, int fetchSize, int flags) throws SQLException {
+    execute(queries, parameterLists, batchHandler, maxRows, fetchSize, flags, false);
+  }
+
+  public synchronized void execute(Query[] queries, ParameterList[] parameterLists,
+      BatchResultHandler batchHandler, int maxRows, int fetchSize, int flags, boolean adaptiveFetch)
+      throws SQLException {
     waitOnLock();
     if (LOGGER.isLoggable(Level.FINEST)) {
       LOGGER.log(Level.FINEST, "  batch execute {0} queries, handler={1}, maxRows={2}, fetchSize={3}, flags={4}",
@@ -494,7 +511,7 @@ public class QueryExecutorImpl extends QueryExecutorBase {
           parameters = SimpleQuery.NO_PARAMETERS;
         }
 
-        sendQuery(query, parameters, maxRows, fetchSize, flags, handler, batchHandler);
+        sendQuery(query, parameters, maxRows, fetchSize, flags, handler, batchHandler, adaptiveFetch);
 
         if (handler.getException() != null) {
           break;
@@ -508,7 +525,7 @@ public class QueryExecutorImpl extends QueryExecutorBase {
         } else {
           sendSync();
         }
-        processResults(handler, flags);
+        processResults(handler, flags, adaptiveFetch);
         estimatedReceiveBufferBytes = 0;
       }
     } catch (IOException e) {
@@ -1404,7 +1421,7 @@ public class QueryExecutorImpl extends QueryExecutorBase {
    */
   private void sendQuery(Query query, V3ParameterList parameters, int maxRows, int fetchSize,
       int flags, ResultHandler resultHandler,
-      BatchResultHandler batchHandler) throws IOException, SQLException {
+      BatchResultHandler batchHandler, boolean adaptiveFetch) throws IOException, SQLException {
     // Now the query itself.
     Query[] subqueries = query.getSubqueries();
     SimpleParameterList[] subparams = parameters.getSubparams();
@@ -1419,6 +1436,9 @@ public class QueryExecutorImpl extends QueryExecutorBase {
 
       // If we saw errors, don't send anything more.
       if (resultHandler.getException() == null) {
+        if (fetchSize != 0) {
+          adaptiveFetchQueryMonitoring.addNewQuery(adaptiveFetch, query);
+        }
         sendOneQuery((SimpleQuery) query, (SimpleParameterList) parameters, maxRows, fetchSize,
             flags);
       }
@@ -1442,6 +1462,9 @@ public class QueryExecutorImpl extends QueryExecutorBase {
         if (subparams != null) {
           subparam = subparams[i];
         }
+        if (fetchSize != 0) {
+          adaptiveFetchQueryMonitoring.addNewQuery(adaptiveFetch, subquery);
+        }
         sendOneQuery((SimpleQuery) subquery, subparam, maxRows, fetchSize, flags);
       }
     }
@@ -2025,6 +2048,11 @@ public class QueryExecutorImpl extends QueryExecutorBase {
   }
 
   protected void processResults(ResultHandler handler, int flags) throws IOException {
+    processResults(handler, flags, false);
+  }
+
+  protected void processResults(ResultHandler handler, int flags, boolean adaptiveFetch)
+      throws IOException {
     boolean noResults = (flags & QueryExecutor.QUERY_NO_RESULTS) != 0;
     boolean bothRowsAndStatus = (flags & QueryExecutor.QUERY_BOTH_ROWS_AND_STATUS) != 0;
 
@@ -2140,6 +2168,13 @@ public class QueryExecutorImpl extends QueryExecutorBase {
           SimpleQuery currentQuery = executeData.query;
           Portal currentPortal = executeData.portal;
 
+          if (currentPortal != null) {
+            // Existence of portal defines if query was using fetching.
+            adaptiveFetchQueryMonitoring
+              .updateQueryFetchSize(adaptiveFetch, currentQuery, pgStream.getMaxRowSize());
+          }
+          pgStream.clearMaxRowSize();
+
           Field[] fields = currentQuery.getFields();
           if (fields != null && tuples == null) {
             // When no results expected, pretend an empty resultset was returned
@@ -2166,6 +2201,17 @@ public class QueryExecutorImpl extends QueryExecutorBase {
           SimpleQuery currentQuery = executeData.query;
           Portal currentPortal = executeData.portal;
 
+          if (currentPortal != null) {
+            //Existence of portal defines if query was using fetching.
+
+            //Command executed, adaptive fetch size can be removed for this query, max row size can be cleared
+            adaptiveFetchQueryMonitoring.removeQuery(adaptiveFetch, currentQuery);
+            //Update to change fetch size for other fetch portals of this query
+            adaptiveFetchQueryMonitoring
+              .updateQueryFetchSize(adaptiveFetch, currentQuery, pgStream.getMaxRowSize());
+          }
+          pgStream.clearMaxRowSize();
+
           if (status.startsWith("SET")) {
             String nativeSql = currentQuery.getNativeQuery().nativeSql;
             // Scan only the first 1024 characters to
@@ -2434,8 +2480,8 @@ public class QueryExecutorImpl extends QueryExecutorBase {
     pgStream.skip(len - 4);
   }
 
-  public synchronized void fetch(ResultCursor cursor, ResultHandler handler, int fetchSize)
-      throws SQLException {
+  public synchronized void fetch(ResultCursor cursor, ResultHandler handler, int fetchSize,
+      boolean adaptiveFetch) throws SQLException {
     waitOnLock();
     final Portal portal = (Portal) cursor;
 
@@ -2458,7 +2504,7 @@ public class QueryExecutorImpl extends QueryExecutorBase {
       sendExecute(portal.getQuery(), portal, fetchSize);
       sendSync();
 
-      processResults(handler, 0);
+      processResults(handler, 0, adaptiveFetch);
       estimatedReceiveBufferBytes = 0;
     } catch (IOException e) {
       abort();
@@ -2470,6 +2516,57 @@ public class QueryExecutorImpl extends QueryExecutorBase {
     handler.handleCompletion();
   }
 
+  @Override
+  public int getAdaptiveFetchSize(boolean adaptiveFetch, ResultCursor cursor) {
+    try {
+      if (cursor instanceof Portal) {
+        return adaptiveFetchQueryMonitoring
+          .getFetchSizeForQuery(adaptiveFetch, ((Portal) cursor).getQuery());
+      }
+    } catch (Exception ex) {
+      LOGGER.log(Level.WARNING,
+          "Exception returned during getting adaptive fetch size from QueryExecutorImpl:\n {0}",
+          ex.getMessage());
+    }
+    return -1;
+  }
+
+  @Override
+  public void setAdaptiveFetch(boolean adaptiveFetch) {
+    this.adaptiveFetchQueryMonitoring.setAdaptiveFetch(adaptiveFetch);
+  }
+
+  @Override
+  public boolean getAdaptiveFetch() {
+    return this.adaptiveFetchQueryMonitoring.getAdaptiveFetch();
+  }
+
+  @Override
+  public void addQueryToAdaptiveFetchMonitoring(boolean adaptiveFetch, ResultCursor cursor) {
+    try {
+      if (cursor instanceof Portal) {
+        adaptiveFetchQueryMonitoring.addNewQuery(adaptiveFetch, ((Portal) cursor).getQuery());
+      }
+    } catch (Exception ex) {
+      LOGGER.log(Level.WARNING,
+          "Exception returned during adding query to Adaptive Fetch Monitoring inside QueryExecutorImpl:\n {0}",
+          ex.getMessage());
+    }
+  }
+
+  @Override
+  public void removeQueryFromAdaptiveFetchMonitoring(boolean adaptiveFetch, ResultCursor cursor) {
+    try {
+      if (cursor instanceof Portal) {
+        adaptiveFetchQueryMonitoring.removeQuery(adaptiveFetch, ((Portal) cursor).getQuery());
+      }
+    } catch (Exception ex) {
+      LOGGER.log(Level.WARNING,
+          "Exception returned during removing query from Adaptive Fetch Monitoring inside QueryExecutorImpl:\n {0}",
+          ex.getMessage());
+    }
+  }
+
   /*
    * Receive the field descriptions from the back end.
    */
diff --git a/pgjdbc/src/main/java/org/postgresql/core/v3/adaptivefetch/AdaptiveFetchQueryInfo.java b/pgjdbc/src/main/java/org/postgresql/core/v3/adaptivefetch/AdaptiveFetchQueryInfo.java
new file mode 100644
index 00000000..7ae09734
--- /dev/null
+++ b/pgjdbc/src/main/java/org/postgresql/core/v3/adaptivefetch/AdaptiveFetchQueryInfo.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ * See the LICENSE file in the project root for more information.
+ */
+
+package org.postgresql.core.v3.adaptivefetch;
+
+public class AdaptiveFetchQueryInfo {
+
+  //holds information about adaptive fetch size for query
+  private int size = -1;
+  //number of queries in execution using that query info
+  private int counter = 0;
+  //max row size saved for query so far
+  private int maxRowSize = -1;
+
+  public int getSize() {
+    return size;
+  }
+
+  public void setSize(int size) {
+    this.size = size;
+  }
+
+  public int getCounter() {
+    return counter;
+  }
+
+  public void setCounter(int counter) {
+    this.counter = counter;
+  }
+
+  public int getMaxRowSize() {
+    return maxRowSize;
+  }
+
+  public void setMaxRowSize(int maxRowSize) {
+    this.maxRowSize = maxRowSize;
+  }
+}
diff --git a/pgjdbc/src/main/java/org/postgresql/core/v3/adaptivefetch/AdaptiveFetchQueryMonitoring.java b/pgjdbc/src/main/java/org/postgresql/core/v3/adaptivefetch/AdaptiveFetchQueryMonitoring.java
new file mode 100644
index 00000000..a8db379a
--- /dev/null
+++ b/pgjdbc/src/main/java/org/postgresql/core/v3/adaptivefetch/AdaptiveFetchQueryMonitoring.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ * See the LICENSE file in the project root for more information.
+ */
+
+package org.postgresql.core.v3.adaptivefetch;
+
+import org.postgresql.PGProperty;
+import org.postgresql.core.Query;
+
+import java.sql.SQLException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * The main purpose of this class is to handle adaptive fetching process. Adaptive fetching is used
+ * to compute fetch size to fully use size defined by maxResultBuffer. Computing is made by dividing
+ * maxResultBuffer size by max row result size noticed so far. Each query have separate adaptive
+ * fetch size computed, but same queries have it shared. If adaptive fetch is turned on, first fetch
+ * is going to be made with defaultRowFetchSize, next fetching of resultSet will be made with
+ * computed adaptive fetch size. If adaptive fetch is turned on during fetching, then first fetching
+ * made by ResultSet will be made with defaultRowFetchSize, next will use computed adaptive fetch
+ * size. Property adaptiveFetch need properties defaultRowFetchSize and maxResultBuffer to work.
+ */
+public class AdaptiveFetchQueryMonitoring {
+
+  private final Map<String, AdaptiveFetchQueryInfo> adaptiveFetchInfoMap;
+  private boolean adaptiveFetch = false;
+  private int minimumAdaptiveFetchSize = 0;
+  private int maximumAdaptiveFetchSize = -1;
+  private long maxResultBufferSize = -1;
+
+  public AdaptiveFetchQueryMonitoring(long maxResultBufferSize, Properties info)
+      throws SQLException {
+    this.adaptiveFetchInfoMap = new HashMap<String, AdaptiveFetchQueryInfo>();
+
+    this.adaptiveFetch = PGProperty.ADAPTIVE_FETCH.getBoolean(info);
+    this.minimumAdaptiveFetchSize = PGProperty.ADAPTIVE_FETCH_MINIMUM.getInt(info);
+    this.maximumAdaptiveFetchSize = PGProperty.ADAPTIVE_FETCH_MAXIMUM.getInt(info);
+
+    this.maxResultBufferSize = maxResultBufferSize;
+  }
+
+  /**
+   * Method to add query to being monitored and computing adaptive fetch size.
+   *
+   * @param adaptiveFetch state of adaptive fetch, which should be used during adding query
+   * @param query         query to be monitored
+   */
+  public void addNewQuery(boolean adaptiveFetch, Query query) {
+    if (adaptiveFetch && maxResultBufferSize != -1) {
+      String sql = query.getNativeSql().trim();
+      AdaptiveFetchQueryInfo adaptiveFetchQueryInfo = adaptiveFetchInfoMap.get(sql);
+      if (adaptiveFetchQueryInfo == null) {
+        adaptiveFetchQueryInfo = new AdaptiveFetchQueryInfo();
+      }
+      adaptiveFetchQueryInfo.setCounter(adaptiveFetchQueryInfo.getCounter() + 1);
+
+      adaptiveFetchInfoMap.put(sql, adaptiveFetchQueryInfo);
+    }
+  }
+
+  /**
+   * Method to update adaptive fetch size for given query.
+   *
+   * @param adaptiveFetch state of adaptive fetch, which should be used during updating fetch size
+   *                      for query
+   * @param query         query to be updated
+   * @param maxRowSize    max row size used during updating information about adaptive fetch size
+   *                      for given query
+   */
+  public void updateQueryFetchSize(boolean adaptiveFetch, Query query, int maxRowSize) {
+    if (adaptiveFetch && maxResultBufferSize != -1) {
+      String sql = query.getNativeSql().trim();
+      AdaptiveFetchQueryInfo adaptiveFetchQueryInfo = adaptiveFetchInfoMap.get(sql);
+      if (adaptiveFetchQueryInfo != null) {
+        int adaptiveMaxRowSize = adaptiveFetchQueryInfo.getMaxRowSize();
+        if (adaptiveMaxRowSize < maxRowSize && maxRowSize > 0) {
+          int newFetchSize = (int) (maxResultBufferSize / maxRowSize);
+          newFetchSize = adjustFetchSize(newFetchSize);
+
+          adaptiveFetchQueryInfo.setMaxRowSize(maxRowSize);
+          adaptiveFetchQueryInfo.setSize(newFetchSize);
+
+          adaptiveFetchInfoMap.put(sql, adaptiveFetchQueryInfo);
+        }
+      }
+    }
+  }
+
+  /**
+   * Method to get adaptive fetch size for given query.
+   *
+   * @param adaptiveFetch state of adaptive fetch, which should be used during getting fetch size
+   *                      for query
+   * @param query         query to which we want get adaptive fetch size
+   * @return adaptive fetch size for query or -1 if size doesn't exist/adaptive fetch state is false
+   */
+  public int getFetchSizeForQuery(boolean adaptiveFetch, Query query) {
+    if (adaptiveFetch && maxResultBufferSize != -1) {
+      String sql = query.getNativeSql().trim();
+      AdaptiveFetchQueryInfo adaptiveFetchQueryInfo = adaptiveFetchInfoMap.get(sql);
+      if (adaptiveFetchQueryInfo != null) {
+        return adaptiveFetchQueryInfo.getSize();
+      }
+    }
+    return -1;
+  }
+
+  /**
+   * Method to remove query information from monitoring.
+   *
+   * @param adaptiveFetch state of adaptive fetch, which should be used during removing fetch size
+   *                      for query
+   * @param query         query to be removed from monitoring
+   */
+  public void removeQuery(boolean adaptiveFetch, Query query) {
+    if (adaptiveFetch && maxResultBufferSize != -1) {
+      String sql = query.getNativeSql().trim();
+      AdaptiveFetchQueryInfo adaptiveFetchQueryInfo = adaptiveFetchInfoMap.get(sql);
+      if (adaptiveFetchQueryInfo != null) {
+        adaptiveFetchQueryInfo.setCounter(adaptiveFetchQueryInfo.getCounter() - 1);
+
+        if (adaptiveFetchQueryInfo.getCounter() < 1) {
+          adaptiveFetchInfoMap.remove(sql);
+        } else {
+          adaptiveFetchInfoMap.put(sql, adaptiveFetchQueryInfo);
+        }
+      }
+    }
+  }
+
+  /**
+   * Method to set maximum and minimum constraints on given value.
+   *
+   * @param actualSize value which should be the computed fetch size
+   * @return value which meet the constraints
+   */
+  private int adjustFetchSize(int actualSize) {
+    int size = adjustMaximumFetchSize(actualSize);
+    size = adjustMinimumFetchSize(size);
+    return size;
+  }
+
+  /**
+   * Method to set minimum constraint on given value.
+   *
+   * @param actualSize value which should be the computed fetch size
+   * @return value which meet the minimum constraint
+   */
+  private int adjustMinimumFetchSize(int actualSize) {
+    if (minimumAdaptiveFetchSize == 0) {
+      return actualSize;
+    }
+    if (minimumAdaptiveFetchSize > actualSize) {
+      return minimumAdaptiveFetchSize;
+    } else {
+      return actualSize;
+    }
+  }
+
+  /**
+   * Method to set maximum constraint on given value.
+   *
+   * @param actualSize value which should be the computed fetch size
+   * @return value which meet the maximum constraint
+   */
+  private int adjustMaximumFetchSize(int actualSize) {
+    if (maximumAdaptiveFetchSize == -1) {
+      return actualSize;
+    }
+    if (maximumAdaptiveFetchSize < actualSize) {
+      return maximumAdaptiveFetchSize;
+    } else {
+      return actualSize;
+    }
+  }
+
+  /**
+   * Method to get state of adaptive fetch.
+   *
+   * @return state of adaptive fetch
+   */
+  public boolean getAdaptiveFetch() {
+    return adaptiveFetch;
+  }
+
+  /**
+   * Method to set state of adaptive fetch.
+   *
+   * @param adaptiveFetch desired state of adaptive fetch
+   */
+  public void setAdaptiveFetch(boolean adaptiveFetch) {
+    this.adaptiveFetch = adaptiveFetch;
+  }
+}
diff --git a/pgjdbc/src/main/java/org/postgresql/ds/common/BaseDataSource.java b/pgjdbc/src/main/java/org/postgresql/ds/common/BaseDataSource.java
index cc5d3021..0ddf0c73 100644
--- a/pgjdbc/src/main/java/org/postgresql/ds/common/BaseDataSource.java
+++ b/pgjdbc/src/main/java/org/postgresql/ds/common/BaseDataSource.java
@@ -1519,6 +1519,30 @@ public abstract class BaseDataSource implements CommonDataSource, Referenceable
     PGProperty.MAX_RESULT_BUFFER.set(properties, maxResultBuffer);
   }
 
+  public boolean getAdaptiveFetch() {
+    return PGProperty.ADAPTIVE_FETCH.getBoolean(properties);
+  }
+
+  public void setAdaptiveFetch(boolean adaptiveFetch) {
+    PGProperty.ADAPTIVE_FETCH.set(properties, adaptiveFetch);
+  }
+
+  public int getAdaptiveFetchMaximum() {
+    return PGProperty.ADAPTIVE_FETCH_MAXIMUM.getIntNoCheck(properties);
+  }
+
+  public void setAdaptiveFetchMaximum(int adaptiveFetchMaximum) {
+    PGProperty.ADAPTIVE_FETCH_MAXIMUM.set(properties, adaptiveFetchMaximum);
+  }
+
+  public int getAdaptiveFetchMinimum() {
+    return PGProperty.ADAPTIVE_FETCH_MINIMUM.getIntNoCheck(properties);
+  }
+
+  public void setAdaptiveFetchMinimum(int adaptiveFetchMinimum) {
+    PGProperty.ADAPTIVE_FETCH_MINIMUM.set(properties, adaptiveFetchMinimum);
+  }
+
   //#if mvn.project.property.postgresql.jdbc.spec >= "JDBC4.1"
   public java.util.logging.Logger getParentLogger() {
     return Logger.getLogger("org.postgresql");
diff --git a/pgjdbc/src/main/java/org/postgresql/hostchooser/HostRequirement.java b/pgjdbc/src/main/java/org/postgresql/hostchooser/HostRequirement.java
index c5b25989..d2ddf887 100644
--- a/pgjdbc/src/main/java/org/postgresql/hostchooser/HostRequirement.java
+++ b/pgjdbc/src/main/java/org/postgresql/hostchooser/HostRequirement.java
@@ -14,9 +14,19 @@ public enum HostRequirement {
       return status != HostStatus.ConnectFail;
     }
   },
+  /**
+   * @deprecated we no longer use the terms master or slave in the driver, or the PostgreSQL
+   *        project.
+   */
+  @Deprecated
   master {
     public boolean allowConnectingTo(HostStatus status) {
-      return status == HostStatus.Master || status == HostStatus.ConnectOK;
+      return primary.allowConnectingTo(status);
+    }
+  },
+  primary {
+    public boolean allowConnectingTo(HostStatus status) {
+      return status == HostStatus.Primary || status == HostStatus.ConnectOK;
     }
   },
   secondary {
@@ -35,17 +45,20 @@ public enum HostRequirement {
   /**
    * <p>The postgreSQL project has decided not to use the term slave to refer to alternate servers.
    * secondary or standby is preferred. We have arbitrarily chosen secondary.
-   * As of Jan 2018 in order not to break existint code we are going to accept both slave or
+   * As of Jan 2018 in order not to break existing code we are going to accept both slave or
    * secondary for names of alternate servers.</p>
    *
    * <p>The current policy is to keep accepting this silently but not document slave, or slave preferSlave</p>
    *
+   * <p>As of Jul 2018 silently deprecate the use of the word master as well</p>
+   *
    * @param targetServerType the value of {@code targetServerType} connection property
    * @return HostRequirement
    */
 
   public static HostRequirement getTargetServerType(String targetServerType) {
-    String allowSlave = targetServerType.replace("lave", "econdary");
+
+    String allowSlave = targetServerType.replace("lave", "econdary").replace("master", "primary");
     return valueOf(allowSlave);
   }
 
diff --git a/pgjdbc/src/main/java/org/postgresql/hostchooser/HostStatus.java b/pgjdbc/src/main/java/org/postgresql/hostchooser/HostStatus.java
index cb72fdaa..d303e8d0 100644
--- a/pgjdbc/src/main/java/org/postgresql/hostchooser/HostStatus.java
+++ b/pgjdbc/src/main/java/org/postgresql/hostchooser/HostStatus.java
@@ -11,6 +11,6 @@ package org.postgresql.hostchooser;
 public enum HostStatus {
   ConnectFail,
   ConnectOK,
-  Master,
+  Primary,
   Secondary
 }
diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/PgConnection.java b/pgjdbc/src/main/java/org/postgresql/jdbc/PgConnection.java
index 72e96394..c2905908 100644
--- a/pgjdbc/src/main/java/org/postgresql/jdbc/PgConnection.java
+++ b/pgjdbc/src/main/java/org/postgresql/jdbc/PgConnection.java
@@ -1823,4 +1823,14 @@ public class PgConnection implements BaseConnection {
     return queryExecutor.getParameterStatus(parameterName);
   }
 
+  @Override
+  public boolean getAdaptiveFetch() {
+    return queryExecutor.getAdaptiveFetch();
+  }
+
+  @Override
+  public void setAdaptiveFetch(boolean adaptiveFetch) {
+    queryExecutor.setAdaptiveFetch(adaptiveFetch);
+  }
+
 }
diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/PgResultSet.java b/pgjdbc/src/main/java/org/postgresql/jdbc/PgResultSet.java
index d626b632..a1006586 100644
--- a/pgjdbc/src/main/java/org/postgresql/jdbc/PgResultSet.java
+++ b/pgjdbc/src/main/java/org/postgresql/jdbc/PgResultSet.java
@@ -118,6 +118,8 @@ public class PgResultSet implements ResultSet, org.postgresql.PGRefCursorResultS
   private Tuple rowBuffer = null; // updateable rowbuffer
 
   protected int fetchSize; // Current fetch size (might be 0).
+  protected int lastUsedFetchSize; // Fetch size used during last fetch
+  protected boolean adaptiveFetch = false;
   protected ResultCursor cursor; // Cursor for fetching additional data.
 
   private Map<String, Integer> columnNameIndexMap; // Speed up findColumn by caching lookups
@@ -138,7 +140,7 @@ public class PgResultSet implements ResultSet, org.postgresql.PGRefCursorResultS
 
   PgResultSet(Query originalQuery, BaseStatement statement, Field[] fields, List<Tuple> tuples,
       ResultCursor cursor, int maxRows, int maxFieldSize, int rsType, int rsConcurrency,
-      int rsHoldability) throws SQLException {
+      int rsHoldability, boolean adaptiveFetch) throws SQLException {
     // Fail-fast on invalid null inputs
     if (tuples == null) {
       throw new NullPointerException("tuples must be non-null");
@@ -157,6 +159,10 @@ public class PgResultSet implements ResultSet, org.postgresql.PGRefCursorResultS
     this.maxFieldSize = maxFieldSize;
     this.resultsettype = rsType;
     this.resultsetconcurrency = rsConcurrency;
+    this.adaptiveFetch = adaptiveFetch;
+
+    //Constructor doesn't have fetch size and can't be sure if fetch size was used so initial value would be the number of rows
+    this.lastUsedFetchSize = tuples.size();
   }
 
   public java.net.URL getURL(int columnIndex) throws SQLException {
@@ -815,6 +821,13 @@ public class PgResultSet implements ResultSet, org.postgresql.PGRefCursorResultS
 
     // Work out how many rows maxRows will let us fetch.
     int fetchRows = fetchSize;
+    int adaptiveFetchRows = connection.getQueryExecutor()
+        .getAdaptiveFetchSize(adaptiveFetch, cursor);
+
+    if (adaptiveFetchRows != -1) {
+      fetchRows = adaptiveFetchRows;
+    }
+
     if (maxRows != 0) {
       if (fetchRows == 0 || rowOffset + fetchRows > maxRows) {
         // Fetch would exceed maxRows, limit it.
@@ -823,7 +836,11 @@ public class PgResultSet implements ResultSet, org.postgresql.PGRefCursorResultS
     }
 
     // Do the actual fetch.
-    connection.getQueryExecutor().fetch(cursor, new CursorResultHandler(), fetchRows);
+    connection.getQueryExecutor()
+        .fetch(cursor, new CursorResultHandler(), fetchRows, adaptiveFetch);
+
+    //After fetch, update last used fetch size (could be useful during adaptive fetch).
+    lastUsedFetchSize = fetchRows;
 
     // Now prepend our one saved row and move to it.
     rows.add(0, thisRow);
@@ -1578,6 +1595,50 @@ public class PgResultSet implements ResultSet, org.postgresql.PGRefCursorResultS
     return updateable;
   }
 
+  /**
+   * Method to turn on/off adaptive fetch for ResultSet.
+   *
+   * @param adaptiveFetch desired state of adaptive fetch.
+   * @throws SQLException exception returned if ResultSet is closed
+   */
+  public void setAdaptiveFetch(boolean adaptiveFetch) throws SQLException {
+    checkClosed();
+    updateQueryInsideAdaptiveFetchMonitoring(adaptiveFetch);
+    this.adaptiveFetch = adaptiveFetch;
+  }
+
+  /**
+   * Method to update adaptive fetch monitoring during changing state of adaptive fetch inside
+   * ResultSet. Update inside AdaptiveFetchMonitoring is required to collect data about max result
+   * row length for that query to compute adaptive fetch size.
+   *
+   * @param newAdaptiveFetch new state of adaptive fetch
+   */
+  private void updateQueryInsideAdaptiveFetchMonitoring(boolean newAdaptiveFetch) {
+    boolean localAdaptiveFetch = this.adaptiveFetch;
+
+    if (localAdaptiveFetch == false && newAdaptiveFetch == true) {
+      //If we are here, that means we want to be added to adaptive fetch.
+      connection.getQueryExecutor().addQueryToAdaptiveFetchMonitoring(Boolean.TRUE, cursor);
+    }
+
+    if (localAdaptiveFetch == true && newAdaptiveFetch == false) {
+      //If we are here, that means we want to be removed from adaptive fetch.
+      connection.getQueryExecutor().removeQueryFromAdaptiveFetchMonitoring(Boolean.TRUE, cursor);
+    }
+  }
+
+  /**
+   * Method to get state of adaptive fetch for resultSet.
+   *
+   * @return state of adaptive fetch (turned on or off)
+   * @throws SQLException exception returned if ResultSet is closed
+   */
+  public boolean getAdaptiveFetch() throws SQLException {
+    checkClosed();
+    return adaptiveFetch;
+  }
+
   /**
    * Cracks out the table name and schema (if it exists) from a fully qualified table name.
    *
@@ -1794,7 +1855,23 @@ public class PgResultSet implements ResultSet, org.postgresql.PGRefCursorResultS
 
   public int getFetchSize() throws SQLException {
     checkClosed();
-    return fetchSize;
+    if (adaptiveFetch) {
+      return lastUsedFetchSize;
+    } else {
+      return fetchSize;
+    }
+  }
+
+  /**
+   * Method to get fetch size used during last fetch. Returned value can be useful if using adaptive
+   * fetch.
+   *
+   * @return fetch size used during last fetch.
+   * @throws SQLException exception returned if ResultSet is closed
+   */
+  public int getLastUsedFetchSize() throws SQLException {
+    checkClosed();
+    return lastUsedFetchSize;
   }
 
   @Override
@@ -1818,6 +1895,13 @@ public class PgResultSet implements ResultSet, org.postgresql.PGRefCursorResultS
       rowOffset += rows.size(); // We are discarding some data.
 
       int fetchRows = fetchSize;
+      int adaptiveFetchRows = connection.getQueryExecutor()
+          .getAdaptiveFetchSize(adaptiveFetch, cursor);
+
+      if (adaptiveFetchRows != -1) {
+        fetchRows = adaptiveFetchRows;
+      }
+
       if (maxRows != 0) {
         if (fetchRows == 0 || rowOffset + fetchRows > maxRows) {
           // Fetch would exceed maxRows, limit it.
@@ -1826,7 +1910,11 @@ public class PgResultSet implements ResultSet, org.postgresql.PGRefCursorResultS
       }
 
       // Execute the fetch and update this resultset.
-      connection.getQueryExecutor().fetch(cursor, new CursorResultHandler(), fetchRows);
+      connection.getQueryExecutor()
+          .fetch(cursor, new CursorResultHandler(), fetchRows, adaptiveFetch);
+
+      //After fetch, update last used fetch size (could be useful for adaptive fetch).
+      lastUsedFetchSize = fetchRows;
 
       currentRow = 0;
 
diff --git a/pgjdbc/src/main/java/org/postgresql/jdbc/PgStatement.java b/pgjdbc/src/main/java/org/postgresql/jdbc/PgStatement.java
index 00c089c3..4469afef 100644
--- a/pgjdbc/src/main/java/org/postgresql/jdbc/PgStatement.java
+++ b/pgjdbc/src/main/java/org/postgresql/jdbc/PgStatement.java
@@ -137,6 +137,8 @@ public class PgStatement implements Statement, BaseStatement {
 
   protected int maxFieldSize = 0;
 
+  protected boolean adaptiveFetch = false;
+
   PgStatement(PgConnection c, int rsType, int rsConcurrency, int rsHoldability)
       throws SQLException {
     this.connection = c;
@@ -145,6 +147,7 @@ public class PgStatement implements Statement, BaseStatement {
     concurrency = rsConcurrency;
     setFetchSize(c.getDefaultFetchSize());
     setPrepareThreshold(c.getPrepareThreshold());
+    setAdaptiveFetch(c.getAdaptiveFetch());
     this.rsHoldability = rsHoldability;
   }
 
@@ -152,7 +155,7 @@ public class PgStatement implements Statement, BaseStatement {
       ResultCursor cursor) throws SQLException {
     PgResultSet newResult = new PgResultSet(originalQuery, this, fields, tuples, cursor,
         getMaxRows(), getMaxFieldSize(), getResultSetType(), getResultSetConcurrency(),
-        getResultSetHoldability());
+        getResultSetHoldability(), getAdaptiveFetch());
     newResult.setFetchSize(getFetchSize());
     newResult.setFetchDirection(getFetchDirection());
     return newResult;
@@ -446,7 +449,7 @@ public class PgStatement implements Statement, BaseStatement {
     try {
       startTimer();
       connection.getQueryExecutor().execute(queryToExecute, queryParameters, handler, maxrows,
-          fetchSize, flags);
+          fetchSize, flags, adaptiveFetch);
     } finally {
       killTimerTask();
     }
@@ -849,7 +852,7 @@ public class PgStatement implements Statement, BaseStatement {
     try {
       startTimer();
       connection.getQueryExecutor().execute(queries, parameterLists, handler, maxrows, fetchSize,
-          flags);
+          flags, adaptiveFetch);
     } finally {
       killTimerTask();
       // There might be some rows generated even in case of failures
@@ -1258,4 +1261,14 @@ public class PgStatement implements Statement, BaseStatement {
   protected void transformQueriesAndParameters() throws SQLException {
   }
 
+  @Override
+  public void setAdaptiveFetch(boolean adaptiveFetch) {
+    this.adaptiveFetch = adaptiveFetch;
+  }
+
+  @Override
+  public boolean getAdaptiveFetch() {
+    return adaptiveFetch;
+  }
+
 }


Test patch:
diff --git a/pgjdbc/src/test/java/org/postgresql/core/v3/adaptivefetch/AdaptiveFetchQueryMonitoringTest.java b/pgjdbc/src/test/java/org/postgresql/core/v3/adaptivefetch/AdaptiveFetchQueryMonitoringTest.java
new file mode 100644
index 00000000..68c4c739
--- /dev/null
+++ b/pgjdbc/src/test/java/org/postgresql/core/v3/adaptivefetch/AdaptiveFetchQueryMonitoringTest.java
@@ -0,0 +1,1087 @@
+/*
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ * See the LICENSE file in the project root for more information.
+ */
+
+package org.postgresql.core.v3.adaptivefetch;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import org.postgresql.PGProperty;
+import org.postgresql.core.ParameterList;
+import org.postgresql.core.Query;
+import org.postgresql.core.SqlCommand;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.lang.reflect.Field;
+import java.sql.SQLException;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * Unit tests for AdaptiveFetchQueryMonitoring class.
+ */
+public class AdaptiveFetchQueryMonitoringTest {
+
+  private AdaptiveFetchQueryMonitoring adaptiveFetchQueryMonitoring;
+  private int size;
+
+  //string containing variable names in AdaptiveFetchQueryMonitoring class
+  private static String infoMapVariableName = "adaptiveFetchInfoMap";
+  private static String minimumSizeVariableName = "minimumAdaptiveFetchSize";
+  private static String maximumSizeVariableName = "maximumAdaptiveFetchSize";
+  private static String adaptiveFetchVariableName = "adaptiveFetch";
+  private static String maxBufferSizeVariableName = "maxResultBufferSize";
+
+  /**
+   * Simple setup to create new AdaptiveFetchQueryMonitoring with buffer size 1000.
+   */
+  @Before
+  public void setUp() throws SQLException {
+    Properties properties = new Properties();
+    size = 1000;
+    adaptiveFetchQueryMonitoring = new AdaptiveFetchQueryMonitoring(size, properties);
+  }
+
+  /**
+   * Tests for calling constructor with empty properties (just asserts after setUp).
+   */
+  @Test
+  public void testConstructorDefault() throws NoSuchFieldException, IllegalAccessException {
+    assertNotNull(getInfoMapVariable());
+    assertEquals(size, getMaxBufferVariable());
+    assertEquals(false, getAdaptiveFetchVariable());
+    assertEquals(0, getMinimumSizeVariable());
+    assertEquals(-1, getMaximumSizeVariable());
+  }
+
+  /**
+   * Test for calling constructor with information about adaptiveFetch property.
+   */
+  @Test
+  public void testConstructorWithAdaptiveFetch()
+      throws SQLException, NoSuchFieldException, IllegalAccessException {
+    Properties properties = new Properties();
+    boolean expectedValue = true;
+    PGProperty.ADAPTIVE_FETCH.set(properties, expectedValue);
+
+    adaptiveFetchQueryMonitoring = new AdaptiveFetchQueryMonitoring(size, properties);
+
+    assertNotNull(getInfoMapVariable());
+    assertEquals(size, getMaxBufferVariable());
+    assertEquals(expectedValue, getAdaptiveFetchVariable());
+    assertEquals(0, getMinimumSizeVariable());
+    assertEquals(-1, getMaximumSizeVariable());
+  }
+
+  /**
+   * Test for calling constructor with information about adaptiveFetchMinimum property.
+   */
+  @Test
+  public void testConstructorWithMinimumSize()
+      throws SQLException, NoSuchFieldException, IllegalAccessException {
+    Properties properties = new Properties();
+    int expectedValue = 100;
+    PGProperty.ADAPTIVE_FETCH_MINIMUM.set(properties, expectedValue);
+
+    adaptiveFetchQueryMonitoring = new AdaptiveFetchQueryMonitoring(size, properties);
+
+    assertNotNull(getInfoMapVariable());
+    assertEquals(size, getMaxBufferVariable());
+    assertEquals(false, getAdaptiveFetchVariable());
+    assertEquals(expectedValue, getMinimumSizeVariable());
+    assertEquals(-1, getMaximumSizeVariable());
+  }
+
+  /**
+   * Test for calling constructor with information about adaptiveFetchMaximum property.
+   */
+  @Test
+  public void testConstructorWithMaximumSize()
+      throws SQLException, NoSuchFieldException, IllegalAccessException {
+    Properties properties = new Properties();
+    int expectedValue = 100;
+    PGProperty.ADAPTIVE_FETCH_MAXIMUM.set(properties, expectedValue);
+
+    adaptiveFetchQueryMonitoring = new AdaptiveFetchQueryMonitoring(size, properties);
+
+    assertNotNull(getInfoMapVariable());
+    assertEquals(size, getMaxBufferVariable());
+    assertEquals(false, getAdaptiveFetchVariable());
+    assertEquals(0, getMinimumSizeVariable());
+    assertEquals(expectedValue, getMaximumSizeVariable());
+  }
+
+  /**
+   * Test for calling constructor with information about adaptiveFetch, adaptiveFetchMinimum and
+   * adaptiveFetchMaximum properties.
+   */
+  @Test
+  public void testConstructorWithAllProperties()
+      throws SQLException, NoSuchFieldException, IllegalAccessException {
+    Properties properties = new Properties();
+    boolean expectedAdaptiveFetchValue = false;
+    int expectedMinimumSizeValue = 70;
+    int expectedMaximumSizeValue = 130;
+    PGProperty.ADAPTIVE_FETCH.set(properties, expectedAdaptiveFetchValue);
+    PGProperty.ADAPTIVE_FETCH_MINIMUM.set(properties, expectedMinimumSizeValue);
+    PGProperty.ADAPTIVE_FETCH_MAXIMUM.set(properties, expectedMaximumSizeValue);
+
+    adaptiveFetchQueryMonitoring = new AdaptiveFetchQueryMonitoring(size, properties);
+
+    assertNotNull(getInfoMapVariable());
+    assertEquals(size, getMaxBufferVariable());
+    assertEquals(expectedAdaptiveFetchValue, getAdaptiveFetchVariable());
+    assertEquals(expectedMinimumSizeValue, getMinimumSizeVariable());
+    assertEquals(expectedMaximumSizeValue, getMaximumSizeVariable());
+  }
+
+
+  /**
+   * Test for calling addNewQuery method.
+   */
+  @Test
+  public void testAddingSingleQuery() throws NoSuchFieldException, IllegalAccessException {
+    String expectedQuery = "test-query";
+    boolean adaptiveFetch = true;
+
+    adaptiveFetchQueryMonitoring.addNewQuery(adaptiveFetch, new MockUpQuery(expectedQuery));
+
+    Map<String, AdaptiveFetchQueryInfo> map = getInfoMapVariable();
+
+    assertEquals(1, map.size());
+    assertNotNull(map.get(expectedQuery));
+  }
+
+  /**
+   * Test for calling addNewQuery method, but adaptiveFetch is set to false.
+   */
+  @Test
+  public void testAddingSingleQueryWithoutAdaptiveFetch()
+      throws NoSuchFieldException, IllegalAccessException {
+    String expectedQuery = "test-query";
+    boolean adaptiveFetch = false;
+
+    adaptiveFetchQueryMonitoring.addNewQuery(adaptiveFetch, new MockUpQuery(expectedQuery));
+
+    Map<String, AdaptiveFetchQueryInfo> map = getInfoMapVariable();
+
+    assertEquals(0, map.size());
+    assertNull(map.get(expectedQuery));
+  }
+
+  /**
+   * Test for calling addNewQuery method twice with the same query. The query should be added only
+   * once, with counter set as 2.
+   */
+  @Test
+  public void testAddingSameQueryTwoTimes() throws NoSuchFieldException, IllegalAccessException {
+    String expectedQuery = "test-query";
+    boolean adaptiveFetch = true;
+
+    adaptiveFetchQueryMonitoring.addNewQuery(adaptiveFetch, new MockUpQuery(expectedQuery));
+    adaptiveFetchQueryMonitoring.addNewQuery(adaptiveFetch, new MockUpQuery(expectedQuery));
+
+    Map<String, AdaptiveFetchQueryInfo> map = getInfoMapVariable();
+
+    assertEquals(1, map.size());
+    assertNotNull(map.get(expectedQuery));
+    assertEquals(2, map.get(expectedQuery).getCounter());
+  }
+
+  /**
+   * Test for calling addNewQuery method twice with the same query, but with adaptiveFetch is set to
+   * false. The query shouldn't be added.
+   */
+  @Test
+  public void testAddingSameQueryTwoTimesWithoutAdaptiveFetch()
+      throws NoSuchFieldException, IllegalAccessException {
+    String expectedQuery = "test-query";
+    boolean adaptiveFetch = false;
+
+    adaptiveFetchQueryMonitoring.addNewQuery(adaptiveFetch, new MockUpQuery(expectedQuery));
+    adaptiveFetchQueryMonitoring.addNewQuery(adaptiveFetch, new MockUpQuery(expectedQuery));
+
+    Map<String, AdaptiveFetchQueryInfo> map = getInfoMapVariable();
+
+    assertEquals(0, map.size());
+    assertNull(map.get(expectedQuery));
+  }
+
+  /**
+   * Test for calling addNewQuery method twice with different queries. Both queries should be
+   * added.
+   */
+  @Test
+  public void testAddingTwoDifferentQueries() throws NoSuchFieldException, IllegalAccessException {
+    String expectedQuery = "test-query-1";
+    String expectedQuery2 = "test-query-2";
+    boolean adaptiveFetch = true;
+
+    adaptiveFetchQueryMonitoring.addNewQuery(adaptiveFetch, new MockUpQuery(expectedQuery));
+    adaptiveFetchQueryMonitoring.addNewQuery(adaptiveFetch, new MockUpQuery(expectedQuery2));
+
+    Map<String, AdaptiveFetchQueryInfo> map = getInfoMapVariable();
+
+    assertEquals(2, map.size());
+    assertNotNull(map.get(expectedQuery));
+    assertEquals(1, map.get(expectedQuery).getCounter());
+    assertNotNull(map.get(expectedQuery2));
+    assertEquals(1, map.get(expectedQuery).getCounter());
+  }
+
+  /**
+   * Test for calling addNewQuery method twice with different queries, but adeptiveFetch is set to
+   * false. Both queries shouldn't be added.
+   */
+  @Test
+  public void testAddingTwoDifferentQueriesWithoutAdaptiveFetch()
+      throws NoSuchFieldException, IllegalAccessException {
+    String expectedQuery = "test-query-1";
+    String expectedQuery2 = "test-query-2";
+    boolean adaptiveFetch = false;
+
+    adaptiveFetchQueryMonitoring.addNewQuery(adaptiveFetch, new MockUpQuery(expectedQuery));
+    adaptiveFetchQueryMonitoring.addNewQuery(adaptiveFetch, new MockUpQuery(expectedQuery2));
+
+    Map<String, AdaptiveFetchQueryInfo> map = getInfoMapVariable();
+
+    assertEquals(0, map.size());
+    assertNull(map.get(expectedQuery));
+  }
+
+  /**
+   * Test for calling getAdaptiveFetch method with value true.
+   */
+  @Test
+  public void testGettingAdaptiveFetchIfTrue()
+      throws NoSuchFieldException, IllegalAccessException {
+    boolean expectedResult = true;
+
+    setAdaptiveFetchVariable(expectedResult);
+
+    assertEquals(expectedResult, adaptiveFetchQueryMonitoring.getAdaptiveFetch());
+  }
+
+  /**
+   * Test for calling getAdaptiveFetch method with value false.
+   */
+  @Test
+  public void testGettingAdaptiveFetchIfFalse()
+      throws NoSuchFieldException, IllegalAccessException {
+    boolean expectedResult = false;
+
+    setAdaptiveFetchVariable(expectedResult);
+
+    assertEquals(expectedResult, adaptiveFetchQueryMonitoring.getAdaptiveFetch());
+  }
+
+  /**
+   * Test for calling getFetchSizeForQuery method for not existing query. Should return value -1.
+   */
+  @Test
+  public void testGettingFetchSizeForNotExistingQuery() {
+    String expectedQuery = "test-query";
+    boolean adaptiveFetch = true;
+
+    int resultSize = adaptiveFetchQueryMonitoring
+        .getFetchSizeForQuery(adaptiveFetch, new MockUpQuery(expectedQuery));
+
+    assertEquals(-1, resultSize);
+  }
+
+  /**
+   * Test for calling getFetchSizeForQuery method for not existing query, but adaptiveFetch is set
+   * to false. Should return value -1.
+   */
+  @Test
+  public void testGettingFetchSizeForNotExistingQueryIfAdaptiveFetchFalse() {
+    String expectedQuery = "test-query";
+    boolean adaptiveFetch = false;
+
+    int resultSize = adaptiveFetchQueryMonitoring
+        .getFetchSizeForQuery(adaptiveFetch, new MockUpQuery(expectedQuery));
+
+    assertEquals(-1, resultSize);
+  }
+
+  /**
+   * Test for calling getFetchSizeForQuery method for existing query. Should return set fetch size
+   * for the query.
+   */
+  @Test
+  public void testGettingFetchSizeForExistingQuery()
+      throws NoSuchFieldException, IllegalAccessException {
+    String expectedQuery = "test-query";
+    boolean adaptiveFetch = true;
+
+    Map<String, AdaptiveFetchQueryInfo> map = getInfoMapVariable();
+
+    int expectedSize = 500;
+
+    AdaptiveFetchQueryInfo adaptiveFetchQueryInfo = new AdaptiveFetchQueryInfo();
+    adaptiveFetchQueryInfo.setSize(expectedSize);
+    map.put(expectedQuery, adaptiveFetchQueryInfo);
+
+    int resultSize = adaptiveFetchQueryMonitoring
+        .getFetchSizeForQuery(adaptiveFetch, new MockUpQuery(expectedQuery));
+
+    assertEquals(expectedSize, resultSize);
+  }
+
+  /**
+   * Test for calling getFetchSizeForQuery method for existing query, but adaptiveFetch is set to
+   * false. Should return value -1.
+   */
+  @Test
+  public void testGettingFetchSizeForExistingQueryIfAdaptiveFetchFalse()
+      throws NoSuchFieldException, IllegalAccessException {
+    String expectedQuery = "test-query";
+    boolean adaptiveFetch = false;
+
+    Map<String, AdaptiveFetchQueryInfo> map = getInfoMapVariable();
+
+    int newSize = 500;
+
+    AdaptiveFetchQueryInfo adaptiveFetchQueryInfo = new AdaptiveFetchQueryInfo();
+    adaptiveFetchQueryInfo.setSize(newSize);
+    map.put(expectedQuery, adaptiveFetchQueryInfo);
+
+    int resultSize = adaptiveFetchQueryMonitoring
+        .getFetchSizeForQuery(adaptiveFetch, new MockUpQuery(expectedQuery));
+
+    assertEquals(-1, resultSize);
+  }
+
+  /**
+   * Test for calling removeQuery method for not existing query. Should nothing happen.
+   */
+  @Test
+  public void testRemovingNotExistingQuery()
+      throws NoSuchFieldException, IllegalAccessException {
+    String expectedQuery = "test-query";
+    boolean adaptiveFetch = true;
+
+    adaptiveFetchQueryMonitoring.removeQuery(adaptiveFetch, new MockUpQuery(expectedQuery));
+
+    Map<String, AdaptiveFetchQueryInfo> map = getInfoMapVariable();
+
+    assertEquals(0, map.size());
+  }
+
+  /**
+   * Test for calling removeQuery method for not existing query, but adaptiveFetch is set false.
+   * Should nothing happen.
+   */
+  @Test
+  public void testRemovingNotExistingQueryIfAdaptiveFetchFalse()
+      throws NoSuchFieldException, IllegalAccessException {
+    String expectedQuery = "test-query";
+    boolean adaptiveFetch = false;
+
+    adaptiveFetchQueryMonitoring.removeQuery(adaptiveFetch, new MockUpQuery(expectedQuery));
+
+    Map<String, AdaptiveFetchQueryInfo> map = getInfoMapVariable();
+
+    assertEquals(0, map.size());
+  }
+
+  /**
+   * Test for calling removeQuery method for existing query. The query should be removed from the
+   * map inside AdaptiveFetchQueryMonitoring.
+   */
+  @Test
+  public void testRemovingExistingQuery()
+      throws NoSuchFieldException, IllegalAccessException {
+    String expectedQuery = "test-query";
+    boolean adaptiveFetch = true;
+
+    Map<String, AdaptiveFetchQueryInfo> map = getInfoMapVariable();
+
+    AdaptiveFetchQueryInfo adaptiveFetchQueryInfo = new AdaptiveFetchQueryInfo();
+    adaptiveFetchQueryInfo.setCounter(1);
+    map.put(expectedQuery, adaptiveFetchQueryInfo);
+
+    assertEquals(1, map.size());
+
+    adaptiveFetchQueryMonitoring.removeQuery(adaptiveFetch, new MockUpQuery(expectedQuery));
+
+    assertEquals(0, map.size());
+    assertNull(map.get(expectedQuery));
+  }
+
+  /**
+   * Test for calling removeQuery method for existing query, but adaptiveFetch is set false. The
+   * query shouldn't be removed.
+   */
+  @Test
+  public void testRemovingExistingQueryIfAdaptiveFetchFalse()
+      throws NoSuchFieldException, IllegalAccessException {
+    String expectedQuery = "test-query";
+    boolean adaptiveFetch = false;
+
+    Map<String, AdaptiveFetchQueryInfo> map = getInfoMapVariable();
+
+    AdaptiveFetchQueryInfo adaptiveFetchQueryInfo = new AdaptiveFetchQueryInfo();
+    adaptiveFetchQueryInfo.setCounter(1);
+    map.put(expectedQuery, adaptiveFetchQueryInfo);
+
+    assertEquals(1, map.size());
+
+    adaptiveFetchQueryMonitoring.removeQuery(adaptiveFetch, new MockUpQuery(expectedQuery));
+
+    assertEquals(1, map.size());
+    assertNotNull(map.get(expectedQuery));
+    assertEquals(1, map.get(expectedQuery).getCounter());
+  }
+
+  /**
+   * Test for calling removeQuery method for existing query with counter set to 2. After first call,
+   * query shouldn't be removed, but counter set to 1. After second call, query should be removed.
+   */
+  @Test
+  public void testRemovingExistingQueryWithLargeCounter()
+      throws NoSuchFieldException, IllegalAccessException {
+    String expectedQuery = "test-query";
+    boolean adaptiveFetch = true;
+
+    Map<String, AdaptiveFetchQueryInfo> map = getInfoMapVariable();
+
+    AdaptiveFetchQueryInfo adaptiveFetchQueryInfo = new AdaptiveFetchQueryInfo();
+    adaptiveFetchQueryInfo.setCounter(2);
+    map.put(expectedQuery, adaptiveFetchQueryInfo);
+
+    adaptiveFetchQueryMonitoring.removeQuery(adaptiveFetch, new MockUpQuery(expectedQuery));
+
+    assertEquals(1, map.size());
+    assertNotNull(map.get(expectedQuery));
+    assertEquals(1, map.get(expectedQuery).getCounter());
+
+    adaptiveFetchQueryMonitoring.removeQuery(adaptiveFetch, new MockUpQuery(expectedQuery));
+
+    assertEquals(0, map.size());
+    assertNull(map.get(expectedQuery));
+  }
+
+  /**
+   * Test for calling removeQuery method for existing query with counter set to 2, but with
+   * adaptiveFetch set false. After both calls query should be removed and counter shouldn't
+   * change.
+   */
+  @Test
+  public void testRemovingExistingQueryWithLargeCounterIfAdaptiveFetchFalse()
+      throws NoSuchFieldException, IllegalAccessException {
+    String expectedQuery = "test-query";
+    boolean adaptiveFetch = false;
+
+    Map<String, AdaptiveFetchQueryInfo> map = getInfoMapVariable();
+
+    AdaptiveFetchQueryInfo adaptiveFetchQueryInfo = new AdaptiveFetchQueryInfo();
+    adaptiveFetchQueryInfo.setCounter(2);
+    map.put(expectedQuery, adaptiveFetchQueryInfo);
+
+    adaptiveFetchQueryMonitoring.removeQuery(adaptiveFetch, new MockUpQuery(expectedQuery));
+
+    assertEquals(1, map.size());
+    assertNotNull(map.get(expectedQuery));
+    assertEquals(2, map.get(expectedQuery).getCounter());
+
+    adaptiveFetchQueryMonitoring.removeQuery(adaptiveFetch, new MockUpQuery(expectedQuery));
+
+    assertEquals(1, map.size());
+    assertNotNull(map.get(expectedQuery));
+    assertEquals(2, map.get(expectedQuery).getCounter());
+  }
+
+  /**
+   * Test for calling removeQuery method for existing query with more queries put in the map. Only
+   * query used in method call should be removed, other shouldn't change.
+   */
+  @Test
+  public void testRemovingExistingQueryWithMoreQueriesMonitored()
+      throws NoSuchFieldException, IllegalAccessException {
+    String expectedQuery = "test-query-1";
+    String expectedQuery2 = "test-query-2";
+    String expectedQuery3 = "test-query-3";
+    boolean adaptiveFetch = true;
+
+    Map<String, AdaptiveFetchQueryInfo> map = getInfoMapVariable();
+
+    int expectedCounter1 = 1;
+    int expectedCounter2 = 37;
+    int expectedCounter3 = 14;
+
+    AdaptiveFetchQueryInfo adaptiveFetchQueryInfo1 = new AdaptiveFetchQueryInfo();
+    adaptiveFetchQueryInfo1.setCounter(expectedCounter1);
+    map.put(expectedQuery, adaptiveFetchQueryInfo1);
+
+    AdaptiveFetchQueryInfo adaptiveFetchQueryInfo2 = new AdaptiveFetchQueryInfo();
+    adaptiveFetchQueryInfo2.setCounter(expectedCounter2);
+    map.put(expectedQuery2, adaptiveFetchQueryInfo2);
+
+    AdaptiveFetchQueryInfo adaptiveFetchQueryInfo3 = new AdaptiveFetchQueryInfo();
+    adaptiveFetchQueryInfo3.setCounter(expectedCounter3);
+    map.put(expectedQuery3, adaptiveFetchQueryInfo3);
+
+    adaptiveFetchQueryMonitoring.removeQuery(adaptiveFetch, new MockUpQuery(expectedQuery));
+
+    AdaptiveFetchQueryInfo resultInfo1 = map.get(expectedQuery);
+    AdaptiveFetchQueryInfo resultInfo2 = map.get(expectedQuery2);
+    AdaptiveFetchQueryInfo resultInfo3 = map.get(expectedQuery3);
+
+    assertEquals(2, map.size());
+    assertNull(resultInfo1);
+    assertNotNull(resultInfo2);
+    assertEquals(adaptiveFetchQueryInfo2, resultInfo2);
+    assertEquals(expectedCounter2, resultInfo2.getCounter());
+    assertNotNull(resultInfo3);
+    assertEquals(adaptiveFetchQueryInfo3, resultInfo3);
+    assertEquals(expectedCounter3, resultInfo3.getCounter());
+  }
+
+  /**
+   * Test for calling removeQuery method for existing query with more queries put in the map, but
+   * adaptiveFetch is set false. Queries shouldn't change
+   */
+  @Test
+  public void testRemovingExistingQueryWithMoreQueriesMonitoredIfAdaptiveFetchFalse()
+      throws NoSuchFieldException, IllegalAccessException {
+    String expectedQuery = "test-query-1";
+    String expectedQuery2 = "test-query-2";
+    String expectedQuery3 = "test-query-3";
+    boolean adaptiveFetch = false;
+
+    Map<String, AdaptiveFetchQueryInfo> map = getInfoMapVariable();
+
+    int expectedCounter1 = 1;
+    int expectedCounter2 = 37;
+    int expectedCounter3 = 14;
+
+    AdaptiveFetchQueryInfo adaptiveFetchQueryInfo1 = new AdaptiveFetchQueryInfo();
+    adaptiveFetchQueryInfo1.setCounter(expectedCounter1);
+    map.put(expectedQuery, adaptiveFetchQueryInfo1);
+
+    AdaptiveFetchQueryInfo adaptiveFetchQueryInfo2 = new AdaptiveFetchQueryInfo();
+    adaptiveFetchQueryInfo2.setCounter(expectedCounter2);
+    map.put(expectedQuery2, adaptiveFetchQueryInfo2);
+
+    AdaptiveFetchQueryInfo adaptiveFetchQueryInfo3 = new AdaptiveFetchQueryInfo();
+    adaptiveFetchQueryInfo3.setCounter(expectedCounter3);
+    map.put(expectedQuery3, adaptiveFetchQueryInfo3);
+
+    adaptiveFetchQueryMonitoring.removeQuery(adaptiveFetch, new MockUpQuery(expectedQuery));
+
+    AdaptiveFetchQueryInfo resultInfo1 = map.get(expectedQuery);
+    AdaptiveFetchQueryInfo resultInfo2 = map.get(expectedQuery2);
+    AdaptiveFetchQueryInfo resultInfo3 = map.get(expectedQuery3);
+
+    assertEquals(3, map.size());
+    assertNotNull(resultInfo1);
+    assertEquals(adaptiveFetchQueryInfo1, resultInfo1);
+    assertEquals(expectedCounter1, resultInfo1.getCounter());
+    assertNotNull(resultInfo2);
+    assertEquals(adaptiveFetchQueryInfo2, resultInfo2);
+    assertEquals(expectedCounter2, resultInfo2.getCounter());
+    assertNotNull(resultInfo3);
+    assertEquals(adaptiveFetchQueryInfo3, resultInfo3);
+    assertEquals(expectedCounter3, resultInfo3.getCounter());
+  }
+
+  /**
+   * Test for calling setAdaptiveFetch method with true value.
+   */
+  @Test
+  public void testSettingAdaptiveFetchAsTrue()
+      throws NoSuchFieldException, IllegalAccessException {
+    boolean expectedAdaptiveFetch = true;
+
+    adaptiveFetchQueryMonitoring.setAdaptiveFetch(expectedAdaptiveFetch);
+
+    boolean resultAdaptiveFetch = getAdaptiveFetchVariable();
+
+    assertEquals(expectedAdaptiveFetch, resultAdaptiveFetch);
+  }
+
+  /**
+   * Test for calling setAdaptiveFetch method with false value.
+   */
+  @Test
+  public void testSettingAdaptiveFetchAsFalse()
+      throws NoSuchFieldException, IllegalAccessException {
+    boolean expectedAdaptiveFetch = false;
+
+    adaptiveFetchQueryMonitoring.setAdaptiveFetch(expectedAdaptiveFetch);
+
+    boolean resultAdaptiveFetch = getAdaptiveFetchVariable();
+
+    assertEquals(expectedAdaptiveFetch, resultAdaptiveFetch);
+  }
+
+  /**
+   * Test for calling updateQueryFetchSize method. Method should update a value for a query.
+   */
+  @Test
+  public void testUpdatingAdaptiveFetchSize()
+      throws NoSuchFieldException, IllegalAccessException {
+    String expectedQuery = "test-query-1";
+    boolean adaptiveFetch = true;
+
+    Map<String, AdaptiveFetchQueryInfo> map = getInfoMapVariable();
+
+    int rowSize = 33;
+    int startSize = size / rowSize - 15;
+
+    AdaptiveFetchQueryInfo adaptiveFetchQueryInfo = new AdaptiveFetchQueryInfo();
+    adaptiveFetchQueryInfo.setSize(startSize);
+    map.put(expectedQuery, adaptiveFetchQueryInfo);
+
+    adaptiveFetchQueryMonitoring
+      .updateQueryFetchSize(adaptiveFetch, new MockUpQuery(expectedQuery), rowSize);
+
+    AdaptiveFetchQueryInfo resultInfo = map.get(expectedQuery);
+
+    assertNotNull(resultInfo);
+    assertEquals(size / rowSize, resultInfo.getSize());
+  }
+
+  /**
+   * Test for calling updateQueryFetchSize method, but adaptiveFetch is set false. Method shouldn't
+   * update any values.
+   */
+  @Test
+  public void testUpdatingAdaptiveFetchSizeIfAdaptiveFetchFalse()
+      throws NoSuchFieldException, IllegalAccessException {
+    String expectedQuery = "test-query-1";
+    boolean adaptiveFetch = false;
+
+    Map<String, AdaptiveFetchQueryInfo> map = getInfoMapVariable();
+
+    int rowSize = 33;
+    int startSize = size / rowSize - 15;
+
+    AdaptiveFetchQueryInfo adaptiveFetchQueryInfo = new AdaptiveFetchQueryInfo();
+    adaptiveFetchQueryInfo.setSize(startSize);
+    map.put(expectedQuery, adaptiveFetchQueryInfo);
+
+    adaptiveFetchQueryMonitoring
+      .updateQueryFetchSize(adaptiveFetch, new MockUpQuery(expectedQuery), rowSize);
+
+    AdaptiveFetchQueryInfo resultInfo = map.get(expectedQuery);
+
+    assertNotNull(resultInfo);
+    assertEquals(startSize, resultInfo.getSize());
+  }
+
+  /**
+   * Test for calling updateQueryFetchSize method for not existing query. Method shouldn't update
+   * any values.
+   */
+  @Test
+  public void testUpdatingAdaptiveFetchSizeForNotExistingQuery()
+      throws NoSuchFieldException, IllegalAccessException {
+    String expectedQuery = "test-query-1";
+    String expectedQuery2 = "test-query-2";
+    boolean adaptiveFetch = true;
+
+    Map<String, AdaptiveFetchQueryInfo> map = getInfoMapVariable();
+
+    int rowSize = 33;
+    int startSize = size / rowSize - 15;
+
+    AdaptiveFetchQueryInfo adaptiveFetchQueryInfo = new AdaptiveFetchQueryInfo();
+    adaptiveFetchQueryInfo.setSize(startSize);
+    map.put(expectedQuery2, adaptiveFetchQueryInfo);
+
+    adaptiveFetchQueryMonitoring
+      .updateQueryFetchSize(adaptiveFetch, new MockUpQuery(expectedQuery), rowSize);
+
+    AdaptiveFetchQueryInfo resultInfo = map.get(expectedQuery);
+    AdaptiveFetchQueryInfo resultInfo2 = map.get(expectedQuery2);
+
+    assertNull(resultInfo);
+    assertNotNull(resultInfo2);
+    assertEquals(adaptiveFetchQueryInfo, resultInfo2);
+    assertEquals(startSize, resultInfo2.getSize());
+    assertEquals(1, map.size());
+  }
+
+  /**
+   * Test for calling updateQueryFetchSize method for not existing query, but adaptiveFetch is set
+   * false. Method shouldn't update any values.
+   */
+  @Test
+  public void testUpdatingAdaptiveFetchSizeForNotExistingQueryIfAdaptiveFetchFalse()
+      throws NoSuchFieldException, IllegalAccessException {
+    String expectedQuery = "test-query-1";
+    String expectedQuery2 = "test-query-2";
+    boolean adaptiveFetch = false;
+
+    Map<String, AdaptiveFetchQueryInfo> map = getInfoMapVariable();
+
+    int rowSize = 33;
+    int startSize = size / rowSize - 15;
+
+    AdaptiveFetchQueryInfo adaptiveFetchQueryInfo = new AdaptiveFetchQueryInfo();
+    adaptiveFetchQueryInfo.setSize(startSize);
+    map.put(expectedQuery2, adaptiveFetchQueryInfo);
+
+    adaptiveFetchQueryMonitoring
+      .updateQueryFetchSize(adaptiveFetch, new MockUpQuery(expectedQuery), rowSize);
+
+    AdaptiveFetchQueryInfo resultInfo = map.get(expectedQuery);
+    AdaptiveFetchQueryInfo resultInfo2 = map.get(expectedQuery2);
+
+    assertNull(resultInfo);
+    assertNotNull(resultInfo2);
+    assertEquals(adaptiveFetchQueryInfo, resultInfo2);
+    assertEquals(startSize, resultInfo2.getSize());
+    assertEquals(1, map.size());
+  }
+
+  /**
+   * Test for calling updateQueryFetchSize method in a situation when there are more queries saved
+   * in a map. The method should only change value for query used in a call.
+   */
+  @Test
+  public void testUpdatingAdaptiveFetchSizeWithMoreQueriesInMap()
+      throws NoSuchFieldException, IllegalAccessException {
+    String expectedQuery = "test-query-1";
+    String expectedQuery2 = "test-query-2";
+    boolean adaptiveFetch = true;
+
+    Map<String, AdaptiveFetchQueryInfo> map = getInfoMapVariable();
+
+    int rowSize = 33;
+    int startSize = size / rowSize - 15;
+
+    AdaptiveFetchQueryInfo adaptiveFetchQueryInfo = new AdaptiveFetchQueryInfo();
+    adaptiveFetchQueryInfo.setSize(startSize);
+    map.put(expectedQuery, adaptiveFetchQueryInfo);
+
+    AdaptiveFetchQueryInfo adaptiveFetchQueryInfo2 = new AdaptiveFetchQueryInfo();
+    adaptiveFetchQueryInfo2.setSize(startSize);
+    map.put(expectedQuery2, adaptiveFetchQueryInfo2);
+
+    adaptiveFetchQueryMonitoring
+      .updateQueryFetchSize(adaptiveFetch, new MockUpQuery(expectedQuery), rowSize);
+
+    AdaptiveFetchQueryInfo resultInfo = map.get(expectedQuery);
+    AdaptiveFetchQueryInfo resultInfo2 = map.get(expectedQuery2);
+
+    assertNotNull(resultInfo);
+    assertEquals(adaptiveFetchQueryInfo, resultInfo);
+    assertEquals(size / rowSize, resultInfo.getSize());
+    assertNotNull(resultInfo2);
+    assertEquals(adaptiveFetchQueryInfo2, resultInfo2);
+    assertEquals(startSize, resultInfo2.getSize());
+    assertEquals(2, map.size());
+  }
+
+  /**
+   * Test for calling updateQueryFetchSize method in a situation when there are more queries saved
+   * in a map, but adaptiveFetch is set false. The method shouldn't change any values.
+   */
+  @Test
+  public void testUpdatingAdaptiveFetchSizeWithMoreQueriesInMapIfAdaptiveFetchFalse()
+      throws NoSuchFieldException, IllegalAccessException {
+    String expectedQuery = "test-query-1";
+    String expectedQuery2 = "test-query-2";
+    boolean adaptiveFetch = false;
+
+    Map<String, AdaptiveFetchQueryInfo> map = getInfoMapVariable();
+
+    int rowSize = 33;
+    int startSize = size / rowSize - 15;
+
+    AdaptiveFetchQueryInfo adaptiveFetchQueryInfo = new AdaptiveFetchQueryInfo();
+    adaptiveFetchQueryInfo.setSize(startSize);
+    map.put(expectedQuery, adaptiveFetchQueryInfo);
+
+    AdaptiveFetchQueryInfo adaptiveFetchQueryInfo2 = new AdaptiveFetchQueryInfo();
+    adaptiveFetchQueryInfo2.setSize(startSize);
+    map.put(expectedQuery2, adaptiveFetchQueryInfo2);
+
+    adaptiveFetchQueryMonitoring
+      .updateQueryFetchSize(adaptiveFetch, new MockUpQuery(expectedQuery), rowSize);
+
+    AdaptiveFetchQueryInfo resultInfo = map.get(expectedQuery);
+    AdaptiveFetchQueryInfo resultInfo2 = map.get(expectedQuery2);
+
+    assertNotNull(resultInfo);
+    assertEquals(adaptiveFetchQueryInfo, resultInfo);
+    assertEquals(startSize, resultInfo.getSize());
+    assertNotNull(resultInfo2);
+    assertEquals(adaptiveFetchQueryInfo2, resultInfo2);
+    assertEquals(startSize, resultInfo2.getSize());
+    assertEquals(2, map.size());
+  }
+
+  /**
+   * Test for calling updateQueryFetchSize method with value to make computed value below minimum
+   * value. The method should update a query to have value of minimum.
+   */
+  @Test
+  public void testUpdatingAdaptiveFetchSizeWithMinimumSize()
+      throws NoSuchFieldException, IllegalAccessException {
+    String expectedQuery = "test-query-1";
+    boolean adaptiveFetch = true;
+
+    int rowSize = size + 1000;
+    int startSize = 2;
+    int expectedSize = 10;
+
+    setMinimumSizeVariable(expectedSize);
+
+    Map<String, AdaptiveFetchQueryInfo> map = getInfoMapVariable();
+
+    AdaptiveFetchQueryInfo adaptiveFetchQueryInfo = new AdaptiveFetchQueryInfo();
+    adaptiveFetchQueryInfo.setSize(startSize);
+    map.put(expectedQuery, adaptiveFetchQueryInfo);
+
+    adaptiveFetchQueryMonitoring
+      .updateQueryFetchSize(adaptiveFetch, new MockUpQuery(expectedQuery), rowSize);
+
+    AdaptiveFetchQueryInfo resultInfo = map.get(expectedQuery);
+
+    assertNotNull(resultInfo);
+    assertEquals(expectedSize, resultInfo.getSize());
+  }
+
+  /**
+   * Test for calling updateQueryFetchSize method with value to make computed value below minimum
+   * value, but adaptiveFetch is set false. The method shouldn't update size for a query.
+   */
+  @Test
+  public void testUpdatingAdaptiveFetchSizeWithMinimumSizeIfAdaptiveFetchFalse()
+      throws NoSuchFieldException, IllegalAccessException {
+    String expectedQuery = "test-query-1";
+    boolean adaptiveFetch = false;
+
+    int rowSize = size + 1000;
+    int startSize = 2;
+    int expectedSize = 10;
+
+    setMinimumSizeVariable(expectedSize);
+
+    Map<String, AdaptiveFetchQueryInfo> map = getInfoMapVariable();
+
+    AdaptiveFetchQueryInfo adaptiveFetchQueryInfo = new AdaptiveFetchQueryInfo();
+    adaptiveFetchQueryInfo.setSize(startSize);
+    map.put(expectedQuery, adaptiveFetchQueryInfo);
+
+    adaptiveFetchQueryMonitoring
+      .updateQueryFetchSize(adaptiveFetch, new MockUpQuery(expectedQuery), rowSize);
+
+    AdaptiveFetchQueryInfo resultInfo = map.get(expectedQuery);
+
+    assertNotNull(resultInfo);
+    assertEquals(startSize, resultInfo.getSize());
+  }
+
+  /**
+   * Test for calling updateQueryFetchSize method with value to make computed value above maximum
+   * value. The method should update a query to have value of maximum.
+   */
+  @Test
+  public void testUpdatingAdaptiveFetchSizeWithMaximumSize()
+      throws NoSuchFieldException, IllegalAccessException {
+    String expectedQuery = "test-query-1";
+    boolean adaptiveFetch = true;
+
+    int rowSize = 1;
+    int startSize = 2;
+    int expectedSize = size / rowSize - 20;
+
+    setMaximumSizeVariable(expectedSize);
+
+    Map<String, AdaptiveFetchQueryInfo> map = getInfoMapVariable();
+
+    AdaptiveFetchQueryInfo adaptiveFetchQueryInfo = new AdaptiveFetchQueryInfo();
+    adaptiveFetchQueryInfo.setSize(startSize);
+    map.put(expectedQuery, adaptiveFetchQueryInfo);
+
+    adaptiveFetchQueryMonitoring
+      .updateQueryFetchSize(adaptiveFetch, new MockUpQuery(expectedQuery), rowSize);
+
+    AdaptiveFetchQueryInfo resultInfo = map.get(expectedQuery);
+
+    assertNotNull(resultInfo);
+    assertEquals(expectedSize, resultInfo.getSize());
+  }
+
+  /**
+   * Test for calling updateQueryFetchSize method with value to make computed value below maximum
+   * value, but adaptiveFetch is set false. The method shouldn't update size for a query.
+   */
+  @Test
+  public void testUpdatingAdaptiveFetchSizeWithMaximumSizeIfAdaptiveFetchFalse()
+      throws NoSuchFieldException, IllegalAccessException {
+    String expectedQuery = "test-query-1";
+    boolean adaptiveFetch = false;
+
+    int rowSize = 1;
+    int startSize = 2;
+    int expectedSize = size / rowSize - 20;
+
+    setMaximumSizeVariable(expectedSize);
+
+    Map<String, AdaptiveFetchQueryInfo> map = getInfoMapVariable();
+
+    AdaptiveFetchQueryInfo adaptiveFetchQueryInfo = new AdaptiveFetchQueryInfo();
+    adaptiveFetchQueryInfo.setSize(startSize);
+    map.put(expectedQuery, adaptiveFetchQueryInfo);
+
+    adaptiveFetchQueryMonitoring
+      .updateQueryFetchSize(adaptiveFetch, new MockUpQuery(expectedQuery), rowSize);
+
+    AdaptiveFetchQueryInfo resultInfo = map.get(expectedQuery);
+
+    assertNotNull(resultInfo);
+    assertEquals(startSize, resultInfo.getSize());
+  }
+
+  //here are methods for retrieving values from adaptiveFetchQueryMonitoring without calling methods
+
+  private Map<String, AdaptiveFetchQueryInfo> getInfoMapVariable()
+      throws IllegalAccessException, NoSuchFieldException {
+    Field field = adaptiveFetchQueryMonitoring.getClass().getDeclaredField(infoMapVariableName);
+    field.setAccessible(true);
+    return (Map<String, AdaptiveFetchQueryInfo>) field.get(adaptiveFetchQueryMonitoring);
+  }
+
+  private int getMinimumSizeVariable() throws NoSuchFieldException, IllegalAccessException {
+    Field field = adaptiveFetchQueryMonitoring.getClass().getDeclaredField(minimumSizeVariableName);
+    field.setAccessible(true);
+    return (Integer) field.get(adaptiveFetchQueryMonitoring);
+  }
+
+  private int getMaximumSizeVariable() throws NoSuchFieldException, IllegalAccessException {
+    Field field = adaptiveFetchQueryMonitoring.getClass().getDeclaredField(maximumSizeVariableName);
+    field.setAccessible(true);
+    return (Integer) field.get(adaptiveFetchQueryMonitoring);
+  }
+
+  private boolean getAdaptiveFetchVariable() throws NoSuchFieldException, IllegalAccessException {
+    Field field = adaptiveFetchQueryMonitoring.getClass()
+        .getDeclaredField(adaptiveFetchVariableName);
+    field.setAccessible(true);
+    return (Boolean) field.get(adaptiveFetchQueryMonitoring);
+  }
+
+  private long getMaxBufferVariable() throws NoSuchFieldException, IllegalAccessException {
+    Field field = adaptiveFetchQueryMonitoring.getClass()
+        .getDeclaredField(maxBufferSizeVariableName);
+    field.setAccessible(true);
+    return (Long) field.get(adaptiveFetchQueryMonitoring);
+  }
+
+  private void setMinimumSizeVariable(int value)
+      throws NoSuchFieldException, IllegalAccessException {
+    Field field = adaptiveFetchQueryMonitoring.getClass().getDeclaredField(minimumSizeVariableName);
+    field.setAccessible(true);
+    field.set(adaptiveFetchQueryMonitoring, value);
+  }
+
+  private void setMaximumSizeVariable(int value)
+      throws NoSuchFieldException, IllegalAccessException {
+    Field field = adaptiveFetchQueryMonitoring.getClass()
+        .getDeclaredField(maxBufferSizeVariableName);
+    field.setAccessible(true);
+    field.set(adaptiveFetchQueryMonitoring, value);
+  }
+
+  private void setAdaptiveFetchVariable(boolean value)
+      throws NoSuchFieldException, IllegalAccessException {
+    Field field = adaptiveFetchQueryMonitoring.getClass()
+        .getDeclaredField(adaptiveFetchVariableName);
+    field.setAccessible(true);
+    field.set(adaptiveFetchQueryMonitoring, value);
+  }
+
+  private void setMaxBufferVariable(long value)
+      throws NoSuchFieldException, IllegalAccessException {
+    Field field = adaptiveFetchQueryMonitoring.getClass()
+        .getDeclaredField(maxBufferSizeVariableName);
+    field.setAccessible(true);
+    field.set(adaptiveFetchQueryMonitoring, value);
+  }
+
+  /**
+   * Class to mock object with Query interface. As AdaptiveFetchQueryMonitoring is using only
+   * getNativeSql method from Query interface, other shouldn't be called.
+   */
+  private class MockUpQuery implements Query {
+
+    public String sql;
+
+    MockUpQuery(String sql) {
+      this.sql = sql;
+    }
+
+    @Override
+    public ParameterList createParameterList() {
+      throw new WrongMethodCallException("Method shouldn't be called.");
+    }
+
+    @Override
+    public String toString(ParameterList parameters) {
+      throw new WrongMethodCallException("Method shouldn't be called.");
+    }
+
+    @Override
+    public String getNativeSql() {
+      return this.sql;
+    }
+
+    @Override
+    public SqlCommand getSqlCommand() {
+      throw new WrongMethodCallException("Method shouldn't be called.");
+    }
+
+    @Override
+    public void close() {
+      throw new WrongMethodCallException("Method shouldn't be called.");
+    }
+
+    @Override
+    public boolean isStatementDescribed() {
+      throw new WrongMethodCallException("Method shouldn't be called.");
+    }
+
+    @Override
+    public boolean isEmpty() {
+      throw new WrongMethodCallException("Method shouldn't be called.");
+    }
+
+    @Override
+    public int getBatchSize() {
+      throw new WrongMethodCallException("Method shouldn't be called.");
+    }
+
+    @Override
+    public Map<String, Integer> getResultSetColumnNameIndexMap() {
+      throw new WrongMethodCallException("Method shouldn't be called.");
+    }
+
+    @Override
+    public Query[] getSubqueries() {
+      throw new WrongMethodCallException("Method shouldn't be called.");
+    }
+  }
+
+  /**
+   * An exception used when method shouldn't be called in MockUpQuery class.
+   */
+  private class WrongMethodCallException extends RuntimeException {
+
+    WrongMethodCallException(String msg) {
+      super(msg);
+    }
+
+  }
+
+}
diff --git a/pgjdbc/src/test/java/org/postgresql/test/hostchooser/MultiHostsConnectionTest.java b/pgjdbc/src/test/java/org/postgresql/test/hostchooser/MultiHostsConnectionTest.java
index 8c202946..a727864b 100644
--- a/pgjdbc/src/test/java/org/postgresql/test/hostchooser/MultiHostsConnectionTest.java
+++ b/pgjdbc/src/test/java/org/postgresql/test/hostchooser/MultiHostsConnectionTest.java
@@ -14,13 +14,14 @@ import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 import static org.postgresql.hostchooser.HostRequirement.any;
-import static org.postgresql.hostchooser.HostRequirement.master;
 import static org.postgresql.hostchooser.HostRequirement.preferSecondary;
+import static org.postgresql.hostchooser.HostRequirement.primary;
 import static org.postgresql.hostchooser.HostRequirement.secondary;
-import static org.postgresql.hostchooser.HostStatus.Master;
+import static org.postgresql.hostchooser.HostStatus.Primary;
 import static org.postgresql.hostchooser.HostStatus.Secondary;
 import static org.postgresql.test.TestUtil.closeDB;
 
+import org.postgresql.PGProperty;
 import org.postgresql.hostchooser.GlobalHostStatusTracker;
 import org.postgresql.hostchooser.HostRequirement;
 import org.postgresql.test.TestUtil;
@@ -45,12 +46,12 @@ public class MultiHostsConnectionTest {
 
   private static final String user = TestUtil.getUser();
   private static final String password = TestUtil.getPassword();
-  private static final String master1 = TestUtil.getServer() + ":" + TestUtil.getPort();
+  private static final String primary1 = TestUtil.getServer() + ":" + TestUtil.getPort();
   private static final String secondary1 = getSecondaryServer1() + ":" + getSecondaryPort1();
   private static final String secondary2 = getSecondaryServer2() + ":" + getSecondaryPort2();
   private static final String fake1 = "127.127.217.217:1";
 
-  private String masterIp;
+  private String primaryIp;
   private String secondaryIP;
   private String secondaryIP2;
   private Connection con;
@@ -68,7 +69,7 @@ public class MultiHostsConnectionTest {
     hostStatusMap = (Map<HostSpec, Object>) field.get(null);
 
     con = TestUtil.openDB();
-    masterIp = getRemoteHostSpec();
+    primaryIp = getRemoteHostSpec();
     closeDB(con);
 
     con = openSecondaryDB();
@@ -101,8 +102,8 @@ public class MultiHostsConnectionTest {
   private static Properties userAndPassword() {
     Properties props = new Properties();
 
-    props.setProperty("user", TestUtil.getUser());
-    props.setProperty("password", TestUtil.getPassword());
+    PGProperty.USER.set(props,TestUtil.getUser());
+    PGProperty.PASSWORD.set(props,TestUtil.getPassword());
     return props;
   }
 
@@ -153,12 +154,12 @@ public class MultiHostsConnectionTest {
     }
 
     Properties props = new Properties();
-    props.setProperty("user", user);
-    props.setProperty("password", password);
-    props.setProperty("targetServerType", hostType.name());
-    props.setProperty("hostRecheckSeconds", "2");
+    PGProperty.USER.set(props,user);
+    PGProperty.PASSWORD.set(props,password);
+    PGProperty.TARGET_SERVER_TYPE.set(props,hostType.name());
+    PGProperty.HOST_RECHECK_SECONDS.set(props, 2);
     if (lb) {
-      props.setProperty("loadBalanceHosts", "true");
+      PGProperty.LOAD_BALANCE_HOSTS.set(props,"true");
     }
 
     StringBuilder sb = new StringBuilder();
@@ -205,70 +206,70 @@ public class MultiHostsConnectionTest {
 
   @Test
   public void testConnectToAny() throws SQLException {
-    getConnection(any, fake1, master1);
-    assertRemote(masterIp);
-    assertGlobalState(master1, "ConnectOK");
+    getConnection(any, fake1, primary1);
+    assertRemote(primaryIp);
+    assertGlobalState(primary1, "ConnectOK");
     assertGlobalState(fake1, "ConnectFail");
 
     getConnection(any, fake1, secondary1);
     assertRemote(secondaryIP);
     assertGlobalState(secondary1, "ConnectOK");
 
-    getConnection(any, fake1, master1);
-    assertRemote(masterIp);
-    assertGlobalState(master1, "ConnectOK");
+    getConnection(any, fake1, primary1);
+    assertRemote(primaryIp);
+    assertGlobalState(primary1, "ConnectOK");
     assertGlobalState(fake1, "ConnectFail");
   }
 
   @Test
   public void testConnectToMaster() throws SQLException {
-    getConnection(master, true, fake1, master1, secondary1);
-    assertRemote(masterIp);
+    getConnection(primary, true, fake1, primary1, secondary1);
+    assertRemote(primaryIp);
     assertGlobalState(fake1, "ConnectFail");
-    assertGlobalState(master1, "Master");
+    assertGlobalState(primary1, "Primary");
     assertGlobalState(secondary1, null);
 
-    getConnection(master, false, fake1, secondary1, master1);
-    assertRemote(masterIp);
+    getConnection(primary, false, fake1, secondary1, primary1);
+    assertRemote(primaryIp);
     assertGlobalState(fake1, "ConnectFail"); // cached
-    assertGlobalState(master1, "Master"); // connected to master
+    assertGlobalState(primary1, "Primary"); // connected to primary
     assertGlobalState(secondary1, "Secondary"); // was unknown, so tried to connect in order
   }
 
   @Test
   public void testConnectToSecondary() throws SQLException {
-    getConnection(secondary, true, fake1, secondary1, master1);
+    getConnection(secondary, true, fake1, secondary1, primary1);
     assertRemote(secondaryIP);
     assertGlobalState(fake1, "ConnectFail");
     assertGlobalState(secondary1, "Secondary");
-    assertGlobalState(master1, null);
+    assertGlobalState(primary1, null);
 
-    getConnection(secondary, false, fake1, master1, secondary1);
+    getConnection(secondary, false, fake1, primary1, secondary1);
     assertRemote(secondaryIP);
     assertGlobalState(fake1, "ConnectFail"); // cached
     assertGlobalState(secondary1, "Secondary"); // connected
-    assertGlobalState(master1, "Master"); // tried as it was unknown
+    assertGlobalState(primary1, "Primary"); // tried as it was unknown
   }
 
   @Test
   public void testConnectToSecondaryFirst() throws SQLException {
-    getConnection(preferSecondary, true, fake1, secondary1, master1);
+    getConnection(preferSecondary, true, fake1, secondary1, primary1);
     assertRemote(secondaryIP);
     assertGlobalState(fake1, "ConnectFail");
     assertGlobalState(secondary1, "Secondary");
-    assertGlobalState(master1, null);
+    assertGlobalState(primary1, null);
 
-    getConnection(secondary, false, fake1, master1, secondary1);
+    getConnection(secondary, false, fake1, primary1, secondary1);
     assertRemote(secondaryIP);
     assertGlobalState(fake1, "ConnectFail");
     assertGlobalState(secondary1, "Secondary");
-    assertGlobalState(master1, "Master"); // tried as it was unknown
+    assertGlobalState(primary1, "Primary"); // tried as it was unknown
 
-    getConnection(preferSecondary, true, fake1, master1, secondary1);
+    getConnection(preferSecondary, true, fake1, primary1, secondary1);
     assertRemote(secondaryIP);
     assertGlobalState(fake1, "ConnectFail");
     assertGlobalState(secondary1, "Secondary");
-    assertGlobalState(master1, "Master");
+    assertGlobalState(primary1, "Primary");
   }
 
   @Test
@@ -285,14 +286,14 @@ public class MultiHostsConnectionTest {
     Set<String> connectedHosts = new HashSet<String>();
     boolean fake1FoundTried = false;
     for (int i = 0; i < 20; ++i) {
-      getConnection(any, true, true, fake1, master1, secondary1);
+      getConnection(any, true, true, fake1, primary1, secondary1);
       connectedHosts.add(getRemoteHostSpec());
       fake1FoundTried |= hostStatusMap.containsKey(hostSpec(fake1));
       if (connectedHosts.size() == 2 && fake1FoundTried) {
         break;
       }
     }
-    assertEquals("Never connected to all hosts", new HashSet<String>(asList(masterIp, secondaryIP)),
+    assertEquals("Never connected to all hosts", new HashSet<String>(asList(primaryIp, secondaryIP)),
         connectedHosts);
     assertTrue("Never tried to connect to fake node", fake1FoundTried);
   }
@@ -302,7 +303,7 @@ public class MultiHostsConnectionTest {
     Set<String> connectedHosts = new HashSet<String>();
     Set<HostSpec> tryConnectedHosts = new HashSet<HostSpec>();
     for (int i = 0; i < 20; ++i) {
-      getConnection(preferSecondary, true, true, fake1, master1, secondary1, secondary2);
+      getConnection(preferSecondary, true, true, fake1, primary1, secondary1, secondary2);
       connectedHosts.add(getRemoteHostSpec());
       tryConnectedHosts.addAll(hostStatusMap.keySet());
       if (tryConnectedHosts.size() == 4) {
@@ -313,11 +314,11 @@ public class MultiHostsConnectionTest {
         connectedHosts);
     assertEquals("Never tried to connect to fake node",4, tryConnectedHosts.size());
 
-    getConnection(preferSecondary, false, true, fake1, master1, secondary1);
+    getConnection(preferSecondary, false, true, fake1, primary1, secondary1);
     assertRemote(secondaryIP);
     connectedHosts.clear();
     for (int i = 0; i < 20; ++i) {
-      getConnection(preferSecondary, false, true, fake1, master1, secondary1, secondary2);
+      getConnection(preferSecondary, false, true, fake1, primary1, secondary1, secondary2);
       connectedHosts.add(getRemoteHostSpec());
       if (connectedHosts.size() == 2) {
         break;
@@ -326,12 +327,12 @@ public class MultiHostsConnectionTest {
     assertEquals("Never connected to all secondary hosts", new HashSet<String>(asList(secondaryIP, secondaryIP2)),
         connectedHosts);
 
-    // connect to master when there's no secondary
-    getConnection(preferSecondary, true, true, fake1, master1);
-    assertRemote(masterIp);
+    // connect to primary when there's no secondary
+    getConnection(preferSecondary, true, true, fake1, primary1);
+    assertRemote(primaryIp);
 
-    getConnection(preferSecondary, false, true, fake1, master1);
-    assertRemote(masterIp);
+    getConnection(preferSecondary, false, true, fake1, primary1);
+    assertRemote(primaryIp);
   }
 
   @Test
@@ -339,7 +340,7 @@ public class MultiHostsConnectionTest {
     Set<String> connectedHosts = new HashSet<String>();
     Set<HostSpec> tryConnectedHosts = new HashSet<HostSpec>();
     for (int i = 0; i < 20; ++i) {
-      getConnection(secondary, true, true, fake1, master1, secondary1, secondary2);
+      getConnection(secondary, true, true, fake1, primary1, secondary1, secondary2);
       connectedHosts.add(getRemoteHostSpec());
       tryConnectedHosts.addAll(hostStatusMap.keySet());
       if (tryConnectedHosts.size() == 4) {
@@ -348,13 +349,13 @@ public class MultiHostsConnectionTest {
     }
     assertEquals("Did not attempt to connect to all salve hosts", new HashSet<String>(asList(secondaryIP, secondaryIP2)),
         connectedHosts);
-    assertEquals("Did not attempt to connect to master and fake node", 4, tryConnectedHosts.size());
+    assertEquals("Did not attempt to connect to primary and fake node", 4, tryConnectedHosts.size());
 
-    getConnection(preferSecondary, false, true, fake1, master1, secondary1);
+    getConnection(preferSecondary, false, true, fake1, primary1, secondary1);
     assertRemote(secondaryIP);
     connectedHosts.clear();
     for (int i = 0; i < 20; ++i) {
-      getConnection(secondary, false, true, fake1, master1, secondary1, secondary2);
+      getConnection(secondary, false, true, fake1, primary1, secondary1, secondary2);
       connectedHosts.add(getRemoteHostSpec());
       if (connectedHosts.size() == 2) {
         break;
@@ -366,33 +367,33 @@ public class MultiHostsConnectionTest {
 
   @Test
   public void testHostRechecks() throws SQLException, InterruptedException {
-    GlobalHostStatusTracker.reportHostStatus(hostSpec(master1), Secondary);
-    GlobalHostStatusTracker.reportHostStatus(hostSpec(secondary1), Master);
+    GlobalHostStatusTracker.reportHostStatus(hostSpec(primary1), Secondary);
+    GlobalHostStatusTracker.reportHostStatus(hostSpec(secondary1), Primary);
     GlobalHostStatusTracker.reportHostStatus(hostSpec(fake1), Secondary);
 
     try {
-      getConnection(master, false, fake1, secondary1, master1);
+      getConnection(primary, false, fake1, secondary1, primary1);
       fail();
     } catch (SQLException ex) {
     }
 
-    GlobalHostStatusTracker.reportHostStatus(hostSpec(master1), Secondary);
-    GlobalHostStatusTracker.reportHostStatus(hostSpec(secondary1), Master);
+    GlobalHostStatusTracker.reportHostStatus(hostSpec(primary1), Secondary);
+    GlobalHostStatusTracker.reportHostStatus(hostSpec(secondary1), Primary);
     GlobalHostStatusTracker.reportHostStatus(hostSpec(fake1), Secondary);
 
     SECONDS.sleep(3);
 
-    getConnection(master, false, secondary1, fake1, master1);
-    assertRemote(masterIp);
+    getConnection(primary, false, secondary1, fake1, primary1);
+    assertRemote(primaryIp);
   }
 
   @Test
   public void testNoGoodHostsRechecksEverything() throws SQLException, InterruptedException {
-    GlobalHostStatusTracker.reportHostStatus(hostSpec(master1), Secondary);
+    GlobalHostStatusTracker.reportHostStatus(hostSpec(primary1), Secondary);
     GlobalHostStatusTracker.reportHostStatus(hostSpec(secondary1), Secondary);
     GlobalHostStatusTracker.reportHostStatus(hostSpec(fake1), Secondary);
 
-    getConnection(master, false, secondary1, fake1, master1);
-    assertRemote(masterIp);
+    getConnection(primary, false, secondary1, fake1, primary1);
+    assertRemote(primaryIp);
   }
 }
diff --git a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/Jdbc2TestSuite.java b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/Jdbc2TestSuite.java
index 653512f8..1092f805 100644
--- a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/Jdbc2TestSuite.java
+++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/Jdbc2TestSuite.java
@@ -13,6 +13,7 @@ import org.postgresql.core.ParserTest;
 import org.postgresql.core.ReturningParserTest;
 import org.postgresql.core.UTF8EncodingTest;
 import org.postgresql.core.v3.V3ParameterListTests;
+import org.postgresql.core.v3.adaptivefetch.AdaptiveFetchQueryMonitoringTest;
 import org.postgresql.jdbc.DeepBatchedInsertStatementTest;
 import org.postgresql.jdbc.NoColumnMetadataIssue1613Test;
 import org.postgresql.jdbc.PgSQLXMLTest;
@@ -40,6 +41,7 @@ import org.junit.runners.Suite;
  */
 @RunWith(Suite.class)
 @Suite.SuiteClasses({
+    AdaptiveFetchQueryMonitoringTest.class,
     ANTTest.class,
     ArrayTest.class,
     BatchedInsertReWriteEnabledTest.class,
diff --git a/pgjdbc/src/test/java/org/postgresql/test/jdbc42/AdaptiveFetchSizeTest.java b/pgjdbc/src/test/java/org/postgresql/test/jdbc42/AdaptiveFetchSizeTest.java
new file mode 100644
index 00000000..5fd3e387
--- /dev/null
+++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc42/AdaptiveFetchSizeTest.java
@@ -0,0 +1,308 @@
+/*
+ * Copyright (c) 2020, PostgreSQL Global Development Group
+ * See the LICENSE file in the project root for more information.
+ */
+
+package org.postgresql.test.jdbc42;
+
+import static org.junit.Assert.assertEquals;
+
+import org.postgresql.PGConnection;
+import org.postgresql.PGProperty;
+import org.postgresql.jdbc.PreferQueryMode;
+import org.postgresql.test.TestUtil;
+
+import org.junit.After;
+import org.junit.Assume;
+import org.junit.Test;
+
+import java.lang.management.ManagementFactory;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Properties;
+
+/**
+ * Integration tests for adaptive fetch process.
+ */
+public class AdaptiveFetchSizeTest {
+
+  private Connection connection;
+  private PreparedStatement statement;
+  private ResultSet resultSet;
+
+  private String table = "test_adaptive_fetch";
+  private String columns = "value VARCHAR";
+
+  /**
+   * Method to drop table and close connection.
+   */
+  @After
+  public void tearDown() throws SQLException {
+    if (connection != null && !connection.isClosed()) {
+      connection.setAutoCommit(true);
+      if (resultSet != null) {
+        resultSet.close();
+      }
+      if (statement != null) {
+        statement.close();
+      }
+      TestUtil.dropTable(connection, table);
+      TestUtil.closeDB(connection);
+    }
+  }
+
+  /**
+   * First integration test. First is created table with rows sizes like 4 x 35B, 1 x 40B, 45 x 30B.
+   * Next fetching is happening. First fetch is using default fetch size, so it returns 4 rows.
+   * After reading 4 rows, new fetch size is computed. As biggest rows size so far was 35B, then
+   * 300/35B = 8 rows. Second fetch is done with 8 rows. First row in this fetch has size 40B, which
+   * gonna change fetch size to 7 rows (300/40B = 7), next fetch reads won't change size and 7 will
+   * be used to the end.
+   * To check if this works correctly checked is:
+   * - if first 4 rows from result set have fetch size as 4;
+   * - if next 8 rows from result set have fetch size as 8;
+   * - if next 38 rows from result set have fetch size as 7;
+   * - check if all 50 rows were read.
+   */
+  @Test
+  public void testAdaptiveFetching() throws SQLException {
+    int startFetchSize = 4;
+    int expectedFirstSize = 8;
+    int expectedSecondSize = 7;
+    int expectedCounter = 50;
+    int resultCounter = 0;
+
+    Properties properties = new Properties();
+    PGProperty.DEFAULT_ROW_FETCH_SIZE.set(properties, startFetchSize);
+    PGProperty.MAX_RESULT_BUFFER.set(properties, "300");
+    PGProperty.ADAPTIVE_FETCH.set(properties, true);
+
+    openConnectionAndCreateTable(properties);
+
+    for (int i = 0; i != expectedCounter; i++) {
+      if (i == 4) {
+        addStringWithSize(40);
+      } else {
+        addStringWithSize(35);
+      }
+    }
+
+    executeFetchingQuery();
+
+    for (int i = 0; i != 4; i++) {
+      resultSet.next();
+      resultCounter++;
+      assertEquals(startFetchSize, resultSet.getFetchSize());
+    }
+    for (int i = 0; i != 8; i++) {
+      resultSet.next();
+      resultCounter++;
+      assertEquals(expectedFirstSize, resultSet.getFetchSize());
+    }
+    while (resultSet.next()) {
+      resultCounter++;
+      assertEquals(expectedSecondSize, resultSet.getFetchSize());
+    }
+
+    assertEquals(expectedCounter, resultCounter);
+  }
+
+  /**
+   * Second integration test. The main purpose of this set is to check if minimum size was used
+   * during adaptive fetching. To a table are added 50 rows with sizes: 1x270B, 49x10B. First fetch
+   * is done with default size 4. As first row from result set have size 270B, then computed size
+   * should be 1 (300/270 = 1), however minimum size set to 10 should make that next fetch should be
+   * done with size 10. After this fetch size shouldn't change to the end.
+   * To check if this works correctly checked is:
+   * - if first 4 rows from result set have fetch size as 4;
+   * - if next 46 rows from result set have fetch size as 10;
+   * - check if all 50 rows were read.
+   */
+  @Test
+  public void testAdaptiveFetchingWithMinimumSize() throws SQLException {
+    int startFetchSize = 4;
+    int expectedSize = 10;
+    int expectedCounter = 50;
+    int resultCounter = 0;
+
+    Properties properties = new Properties();
+    PGProperty.DEFAULT_ROW_FETCH_SIZE.set(properties, startFetchSize);
+    PGProperty.MAX_RESULT_BUFFER.set(properties, "300");
+    PGProperty.ADAPTIVE_FETCH.set(properties, true);
+    PGProperty.ADAPTIVE_FETCH_MINIMUM.set(properties, expectedSize);
+
+    openConnectionAndCreateTable(properties);
+
+    for (int i = 0; i != expectedCounter; i++) {
+      if (i == 0) {
+        addStringWithSize(270);
+      } else {
+        addStringWithSize(10);
+      }
+    }
+
+    executeFetchingQuery();
+
+    for (int i = 0; i != 4; i++) {
+      resultSet.next();
+      resultCounter++;
+      assertEquals(startFetchSize, resultSet.getFetchSize());
+    }
+    while (resultSet.next()) {
+      resultCounter++;
+      assertEquals(expectedSize, resultSet.getFetchSize());
+    }
+
+    assertEquals(expectedCounter, resultCounter);
+  }
+
+  /**
+   * Third integration test. The main purpose of this set is to check if maximum size was used
+   * during adaptive fetching. To a table are added 50 rows with sizes: 4x10B, 46x30B. First fetch
+   * is done with default size 4. As first fetch have only rows with size 10B, then computed fetch
+   * size should be 30 (300/10 = 30), however maximum size set to 10 should make that next fetch
+   * should be done with size 10 (in other situation next rows will exceed size of maxResultBuffer).
+   * After this fetch size shouldn't change to the end.
+   * To check if this works correctly checked is:
+   * - if first 4 rows from result set have fetch size as 4;
+   * - if next 46 rows from result set have fetch size as 10;
+   * - check if all 50 rows were read.
+   */
+  @Test
+  public void testAdaptiveFetchingWithMaximumSize() throws SQLException {
+    int startFetchSize = 4;
+    int expectedSize = 10;
+    int expectedCounter = 50;
+    int resultCounter = 0;
+
+    Properties properties = new Properties();
+    PGProperty.DEFAULT_ROW_FETCH_SIZE.set(properties, startFetchSize);
+    PGProperty.MAX_RESULT_BUFFER.set(properties, "300");
+    PGProperty.ADAPTIVE_FETCH.set(properties, true);
+    PGProperty.ADAPTIVE_FETCH_MAXIMUM.set(properties, expectedSize);
+
+    openConnectionAndCreateTable(properties);
+
+    for (int i = 0; i != expectedCounter; i++) {
+      if (i < 4) {
+        addStringWithSize(10);
+      } else {
+        addStringWithSize(30);
+      }
+    }
+
+    executeFetchingQuery();
+
+    for (int i = 0; i != 4; i++) {
+      resultSet.next();
+      resultCounter++;
+      assertEquals(startFetchSize, resultSet.getFetchSize());
+    }
+    while (resultSet.next()) {
+      resultCounter++;
+      assertEquals(expectedSize, resultSet.getFetchSize());
+    }
+
+    assertEquals(expectedCounter, resultCounter);
+  }
+
+  /**
+   * Third integration test. The main purpose of this set is to do fetching with maximum possible
+   * buffer. To a table are added 1000 rows with sizes 10B each. First fetch is done with default
+   * size 4, then second fetch should have size computed on maxResultBuffer, most probably that
+   * second fetch would be the last.
+   * To check if this works correctly checked is:
+   * - if first 4 rows from result set have fetch size as 4;
+   * - if next 996 rows from result set have fetch size computed with using max size of
+   * maxResultBuffer;
+   * - check if all 1000 rows were read.
+   */
+  @Test
+  public void testAdaptiveFetchingWithMoreData() throws SQLException {
+    int startFetchSize = 4;
+    int expectedCounter = 1000;
+    int resultCounter = 0;
+    int expectedSize = (int) (
+        (long) (0.90 * ManagementFactory.getMemoryMXBean().getHeapMemoryUsage().getMax()) / 10);
+
+    Properties properties = new Properties();
+    PGProperty.DEFAULT_ROW_FETCH_SIZE.set(properties, startFetchSize);
+    PGProperty.MAX_RESULT_BUFFER.set(properties, "90p");
+    PGProperty.ADAPTIVE_FETCH.set(properties, true);
+
+    openConnectionAndCreateTable(properties);
+
+    for (int i = 0; i != expectedCounter; i++) {
+      addStringWithSize(10);
+    }
+
+    executeFetchingQuery();
+
+    for (int i = 0; i != 4; i++) {
+      resultSet.next();
+      resultCounter++;
+      assertEquals(startFetchSize, resultSet.getFetchSize());
+    }
+    while (resultSet.next()) {
+      resultCounter++;
+      assertEquals(expectedSize, resultSet.getFetchSize());
+    }
+
+    assertEquals(expectedCounter, resultCounter);
+  }
+
+  /**
+   * Method to execute query, which gonna be fetched. Sets auto commit to false to make fetching
+   * happen.
+   */
+  private void executeFetchingQuery() throws SQLException {
+    connection.setAutoCommit(false);
+
+    statement = connection.prepareStatement("SELECT * FROM " + table);
+    resultSet = statement.executeQuery();
+  }
+
+  /**
+   * Method to insert string with given size to a table.
+   *
+   * @param size desired size of a string to be inserted in the table
+   */
+  private void addStringWithSize(int size) throws SQLException {
+    StringBuilder sb = new StringBuilder(size + 2);
+    sb.append("'");
+    for (int i = 0; i != size; i++) {
+      sb.append('H');
+    }
+    sb.append("'");
+    String insert = TestUtil.insertSQL(table, "value", sb.toString());
+    TestUtil.execute(insert, connection);
+  }
+
+  /**
+   * Method to open connection, check if fetch can be performed and create table.
+   *
+   * @param properties Properties to be used during opening connection.
+   */
+  private void openConnectionAndCreateTable(Properties properties) throws SQLException {
+    connection = TestUtil.openDB(properties);
+    //After opening connection we should check if will be possible to do a fetch
+    checkIfFetchTestCanBePerformed(connection);
+    TestUtil.createTable(connection, table, columns);
+  }
+
+  /**
+   * Method to check if a fetch can be performed - PreferQueryMode is not set to Simple.
+   *
+   * @param connection Connection to be checked.
+   */
+  private void checkIfFetchTestCanBePerformed(Connection connection) throws SQLException {
+    PGConnection pgConnection = connection.unwrap(PGConnection.class);
+    PreferQueryMode preferQueryMode =
+        pgConnection == null ? PreferQueryMode.EXTENDED : pgConnection.getPreferQueryMode();
+    Assume.assumeTrue("Fetching tests can't be performed in simple mode",
+        preferQueryMode != PreferQueryMode.SIMPLE);
+  }
+
+}
diff --git a/pgjdbc/src/test/java/org/postgresql/test/jdbc42/Jdbc42TestSuite.java b/pgjdbc/src/test/java/org/postgresql/test/jdbc42/Jdbc42TestSuite.java
index 3d444fd0..6b471c38 100644
--- a/pgjdbc/src/test/java/org/postgresql/test/jdbc42/Jdbc42TestSuite.java
+++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc42/Jdbc42TestSuite.java
@@ -11,6 +11,7 @@ import org.junit.runners.Suite.SuiteClasses;
 
 @RunWith(Suite.class)
 @SuiteClasses({
+    AdaptiveFetchSizeTest.class,
     CustomizeDefaultFetchSizeTest.class,
     GetObject310InfinityTests.class,
     GetObject310Test.class,