socketAddresses = new ArrayList<>();
- this.addrs = Arrays.copyOf(addrs, addrs.length);
-
- sockAddrs = new InetSocketAddress[this.addrs.length];
-
- for (int i = 0; i < this.addrs.length; i++) {
- sockAddrs[i] = parseAddress(this.addrs[i]);
- }
- }
+ /**
+ * Current position within {@link #socketAddresses} list.
+ *
+ * It is {@link #UNSET_POSITION} when no addresses from
+ * the {@link #socketAddresses} pool have been processed yet.
+ *
+ * When this provider receives new addresses it tries
+ * to look for a new position for the last used address or
+ * sets the position to {@link #UNSET_POSITION} otherwise.
+ *
+ * @see #getLastObtainedAddress()
+ * @see #refreshAddresses(Collection)
+ */
+ private AtomicInteger currentPosition = new AtomicInteger(UNSET_POSITION);
/**
- * @return Configured addresses in a form of [host]:[port].
+ * Address list lock for a thread-safe access to it
+ * when a refresh operation occurs.
+ *
+ * @see RefreshableSocketProvider#refreshAddresses(Collection)
*/
- public String[] getAddresses() {
- return this.addrs;
- }
+ private ReadWriteLock addressListLock = new ReentrantReadWriteLock();
/**
- * Sets maximum amount of time to wait for a socket connection establishment
- * with an individual server.
+ * Constructs an instance.
*
- * Zero means infinite timeout.
+ * @param addresses optional array of addresses in a form of host[:port]
*
- * @param timeout Timeout value, ms.
- * @return {@code this}.
- * @throws IllegalArgumentException If timeout is negative.
+ * @throws IllegalArgumentException if addresses aren't provided
*/
- public RoundRobinSocketProviderImpl setTimeout(int timeout) {
- if (timeout < 0)
- throw new IllegalArgumentException("timeout is negative.");
-
- this.timeout = timeout;
+ public RoundRobinSocketProviderImpl(String... addresses) {
+ updateAddressList(Arrays.asList(addresses));
+ setRetriesLimit(DEFAULT_RETRIES_PER_CONNECTION);
+ }
- return this;
+ private void updateAddressList(Collection addresses) {
+ if (addresses == null || addresses.isEmpty()) {
+ throw new IllegalArgumentException("At least one address must be provided");
+ }
+ Lock writeLock = addressListLock.writeLock();
+ writeLock.lock();
+ try {
+ InetSocketAddress lastAddress = getLastObtainedAddress();
+ socketAddresses.clear();
+ addresses.stream()
+ .map(this::parseAddress)
+ .collect(Collectors.toCollection(() -> socketAddresses));
+ if (lastAddress != null) {
+ int recoveredPosition = socketAddresses.indexOf(lastAddress);
+ currentPosition.set(recoveredPosition);
+ } else {
+ currentPosition.set(UNSET_POSITION);
+ }
+ } finally {
+ writeLock.unlock();
+ }
}
/**
- * @return Maximum amount of time to wait for a socket connection establishment
- * with an individual server.
+ * Gets parsed and resolved internet addresses.
+ *
+ * @return socket addresses
*/
- public int getTimeout() {
- return timeout;
+ public List getAddresses() {
+ Lock readLock = addressListLock.readLock();
+ readLock.lock();
+ try {
+ return Collections.unmodifiableList(this.socketAddresses);
+ } finally {
+ readLock.unlock();
+ }
}
/**
- * Sets maximum amount of reconnect attempts to be made before an exception is raised.
- * The retry count is maintained by a {@link #get(int, Throwable)} caller
- * when a socket level connection was established.
- *
- * Negative value means unlimited.
+ * Gets last used address from the pool if it exists.
*
- * @param retriesLimit Limit of retries to use.
- * @return {@code this}.
+ * @return last obtained address or null
+ * if {@link #currentPosition} has {@link #UNSET_POSITION} value
*/
- public RoundRobinSocketProviderImpl setRetriesLimit(int retriesLimit) {
- this.retriesLimit = retriesLimit;
-
- return this;
+ protected InetSocketAddress getLastObtainedAddress() {
+ Lock readLock = addressListLock.readLock();
+ readLock.lock();
+ try {
+ int index = currentPosition.get();
+ return index != UNSET_POSITION ? socketAddresses.get(index) : null;
+ } finally {
+ readLock.unlock();
+ }
}
/**
- * @return Maximum reconnect attempts to make before raising exception.
+ * Tries to open a socket channel to a next instance
+ * for the addresses list.
+ *
+ * There are {@link #getRetriesLimit()} attempts per
+ * call to initiate a connection to the instance.
+ *
+ * @param retryNumber reconnection attempt number
+ * @param lastError reconnection reason
+ *
+ * @return opened socket channel
+ *
+ * @throws IOException if any IO errors occur
+ * @throws CommunicationException if retry number exceeds addresses size
+ *
+ * @see #setRetriesLimit(int)
+ * @see #getAddresses()
*/
- public int getRetriesLimit() {
- return retriesLimit;
- }
-
- /** {@inheritDoc} */
@Override
- public SocketChannel get(int retryNumber, Throwable lastError) {
- if (areRetriesExhausted(retryNumber)) {
- throw new CommunicationException("Connection retries exceeded.", lastError);
+ protected SocketChannel makeAttempt(int retryNumber, Throwable lastError) throws IOException {
+ if (retryNumber > getAddressCount()) {
+ throwFatalError("No more connection addresses are left.");
}
- int attempts = getAddressCount();
- long deadline = System.currentTimeMillis() + timeout * attempts;
- while (!Thread.currentThread().isInterrupted()) {
- SocketChannel channel = null;
+
+ int retriesLimit = getRetriesLimit();
+ InetSocketAddress socketAddress = getNextSocketAddress();
+ IOException connectionError = null;
+ for (int i = 0; i < retriesLimit; i++) {
try {
- channel = SocketChannel.open();
- InetSocketAddress addr = getNextSocketAddress();
- channel.socket().connect(addr, timeout);
- return channel;
+ return openChannel(socketAddress);
} catch (IOException e) {
- if (channel != null) {
- try {
- channel.close();
- } catch (IOException ignored) {
- // No-op.
- }
- }
- long now = System.currentTimeMillis();
- if (deadline <= now) {
- throw new CommunicationException("Connection time out.", e);
- }
- if (--attempts == 0) {
- // Tried all addresses without any lack, but still have time.
- attempts = getAddressCount();
- try {
- Thread.sleep((deadline - now) / attempts);
- } catch (InterruptedException ignored) {
- Thread.currentThread().interrupt();
- }
- }
+ connectionError = e;
}
}
- throw new CommunicationException("Thread interrupted.", new InterruptedException());
+ throw connectionError;
}
/**
- * @return Number of configured addresses.
+ * Sets a retries count per instance.
+ * 0 (infinite) count is not supported by this provider.
+ *
+ * @param retriesLimit limit of retries to use.
*/
- protected int getAddressCount() {
- return sockAddrs.length;
+ @Override
+ public void setRetriesLimit(int retriesLimit) {
+ if (retriesLimit == 0) {
+ throwFatalError("Retries count should be at least 1 or more");
+ }
+ super.setRetriesLimit(retriesLimit);
}
/**
- * @return Socket address to use for the next reconnection attempt.
+ * Gets size of addresses pool.
+ *
+ * @return Number of configured addresses.
*/
- protected InetSocketAddress getNextSocketAddress() {
- InetSocketAddress res = sockAddrs[pos];
- pos = (pos + 1) % sockAddrs.length;
- return res;
+ protected int getAddressCount() {
+ Lock readLock = addressListLock.readLock();
+ readLock.lock();
+ try {
+ return socketAddresses.size();
+ } finally {
+ readLock.unlock();
+ }
}
/**
- * Parse a string address in the form of [host]:[port]
- * and builds a socket address.
+ * Gets next address from the pool to be used to connect.
*
- * @param addr Server address.
- * @return Socket address.
+ * @return Socket address to use for the next reconnection attempt
*/
- protected InetSocketAddress parseAddress(String addr) {
- int idx = addr.indexOf(':');
- String host = (idx < 0) ? addr : addr.substring(0, idx);
- int port = (idx < 0) ? 3301 : Integer.parseInt(addr.substring(idx + 1));
- return new InetSocketAddress(host, port);
+ protected InetSocketAddress getNextSocketAddress() {
+ Lock readLock = addressListLock.readLock();
+ readLock.lock();
+ try {
+ int position = currentPosition.updateAndGet(i -> (i + 1) % socketAddresses.size());
+ return socketAddresses.get(position);
+ } finally {
+ readLock.unlock();
+ }
}
/**
- * Provides a decision on whether retries limit is hit.
+ * Update addresses pool by new list.
+ *
+ * @param addresses list of addresses to be applied
*
- * @param retries Current count of retries.
- * @return {@code true} if retries are exhausted.
+ * @throws IllegalArgumentException if addresses list is empty
*/
- private boolean areRetriesExhausted(int retries) {
- int limit = getRetriesLimit();
- if (limit < 0)
- return false;
- return retries >= limit;
+ public void refreshAddresses(Collection addresses) {
+ updateAddressList(addresses);
}
+
+ private void throwFatalError(String message) {
+ throw new CommunicationException(message);
+ }
+
}
diff --git a/src/main/java/org/tarantool/SingleSocketChannelProviderImpl.java b/src/main/java/org/tarantool/SingleSocketChannelProviderImpl.java
new file mode 100644
index 00000000..ef1fc59b
--- /dev/null
+++ b/src/main/java/org/tarantool/SingleSocketChannelProviderImpl.java
@@ -0,0 +1,62 @@
+package org.tarantool;
+
+import org.tarantool.util.StringUtils;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.nio.channels.SocketChannel;
+
+/**
+ * Simple provider that produces a single connection.
+ * To be used with {@link TarantoolClientImpl}.
+ */
+public class SingleSocketChannelProviderImpl extends BaseSocketChannelProvider {
+
+ private InetSocketAddress address;
+
+ /**
+ * Creates a simple provider.
+ *
+ * @param address instance address
+ */
+ public SingleSocketChannelProviderImpl(String address) {
+ setAddress(address);
+ }
+
+ public SocketAddress getAddress() {
+ return address;
+ }
+
+ @Override
+ protected SocketChannel makeAttempt(int retryNumber, Throwable lastError) throws IOException {
+ if (areRetriesExhausted(retryNumber)) {
+ throw new CommunicationException("Connection retries exceeded.", lastError);
+ }
+ return openChannel(address);
+ }
+
+ /**
+ * Provides a decision on whether retries limit is hit.
+ *
+ * @param retryNumber current count of retries.
+ *
+ * @return {@code true} if retries are exhausted.
+ */
+ private boolean areRetriesExhausted(int retryNumber) {
+ int limit = getRetriesLimit();
+ if (limit < 1) {
+ return false;
+ }
+ return retryNumber >= limit;
+ }
+
+ public void setAddress(String address) {
+ if (StringUtils.isBlank(address)) {
+ throw new IllegalArgumentException("address must not be empty");
+ }
+
+ this.address = parseAddress(address);
+ }
+
+}
diff --git a/src/main/java/org/tarantool/SocketChannelProvider.java b/src/main/java/org/tarantool/SocketChannelProvider.java
index 09112dec..a811bb1f 100644
--- a/src/main/java/org/tarantool/SocketChannelProvider.java
+++ b/src/main/java/org/tarantool/SocketChannelProvider.java
@@ -1,15 +1,22 @@
package org.tarantool;
-
+import java.net.SocketAddress;
import java.nio.channels.SocketChannel;
public interface SocketChannelProvider {
+
/**
* Provides socket channel to init restore connection.
- * You could change hosts on fail and sleep between retries in this method
- * @param retryNumber number of current retry. Reset after successful connect.
+ * You could change hosts between retries in this method.
+ *
+ * @param retryNumber number of current retry.
* @param lastError the last error occurs when reconnecting
- * @return the result of SocketChannel open(SocketAddress remote) call
+ *
+ * @return the result of {@link SocketChannel#open(SocketAddress)} call
+ *
+ * @throws SocketProviderTransientException if recoverable error occurred
+ * @throws RuntimeException if any other reasons occurred
*/
SocketChannel get(int retryNumber, Throwable lastError);
+
}
diff --git a/src/main/java/org/tarantool/SocketProviderTransientException.java b/src/main/java/org/tarantool/SocketProviderTransientException.java
new file mode 100644
index 00000000..5a378903
--- /dev/null
+++ b/src/main/java/org/tarantool/SocketProviderTransientException.java
@@ -0,0 +1,9 @@
+package org.tarantool;
+
+public class SocketProviderTransientException extends RuntimeException {
+
+ public SocketProviderTransientException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
diff --git a/src/main/java/org/tarantool/SqlProtoUtils.java b/src/main/java/org/tarantool/SqlProtoUtils.java
index f84b406b..23a339fd 100644
--- a/src/main/java/org/tarantool/SqlProtoUtils.java
+++ b/src/main/java/org/tarantool/SqlProtoUtils.java
@@ -11,10 +11,10 @@ public abstract class SqlProtoUtils {
public static List> readSqlResult(TarantoolPacket pack) {
List> data = (List>) pack.getBody().get(Key.DATA.getId());
- List> values = new ArrayList>(data.size());
- List metaData = getSQLMetadata(pack);
- LinkedHashMap value = new LinkedHashMap();
+ List> values = new ArrayList<>(data.size());
+ List metaData = getSQLMetadata(pack);
for (List row : data) {
+ LinkedHashMap value = new LinkedHashMap<>();
for (int i = 0; i < row.size(); i++) {
value.put(metaData.get(i).getName(), row.get(i));
}
@@ -27,11 +27,11 @@ public static List> getSQLData(TarantoolPacket pack) {
return (List>) pack.getBody().get(Key.DATA.getId());
}
- public static List getSQLMetadata(TarantoolPacket pack) {
+ public static List getSQLMetadata(TarantoolPacket pack) {
List> meta = (List>) pack.getBody().get(Key.SQL_METADATA.getId());
- List values = new ArrayList(meta.size());
+ List values = new ArrayList<>(meta.size());
for (Map c : meta) {
- values.add(new TarantoolBase.SQLMetaData((String) c.get(Key.SQL_FIELD_NAME.getId())));
+ values.add(new SQLMetaData((String) c.get(Key.SQL_FIELD_NAME.getId())));
}
return values;
}
@@ -44,4 +44,23 @@ public static Long getSqlRowCount(TarantoolPacket pack) {
}
return null;
}
+
+ public static class SQLMetaData {
+ protected String name;
+
+ public SQLMetaData(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public String toString() {
+ return "SQLMetaData{" +
+ "name='" + name + '\'' +
+ '}';
+ }
+ }
}
diff --git a/src/main/java/org/tarantool/TarantoolBase.java b/src/main/java/org/tarantool/TarantoolBase.java
index 2babde7a..6856a495 100644
--- a/src/main/java/org/tarantool/TarantoolBase.java
+++ b/src/main/java/org/tarantool/TarantoolBase.java
@@ -11,8 +11,9 @@
public abstract class TarantoolBase extends AbstractTarantoolOps, Object, Result> {
protected String serverVersion;
+
/**
- * Connection state
+ * Connection state.
*/
protected MsgPackLite msgPackLite = MsgPackLite.INSTANCE;
protected AtomicLong syncId = new AtomicLong();
@@ -31,25 +32,6 @@ public TarantoolBase(String username, String password, Socket socket) {
}
}
- protected static class SQLMetaData {
- protected String name;
-
- public SQLMetaData(String name) {
- this.name = name;
- }
-
- public String getName() {
- return name;
- }
-
- @Override
- public String toString() {
- return "SQLMetaData{" +
- "name='" + name + '\'' +
- '}';
- }
- }
-
protected TarantoolException serverError(long code, Object error) {
return new TarantoolException(code, error instanceof String ? (String) error : new String((byte[]) error));
}
@@ -59,7 +41,7 @@ protected void closeChannel(SocketChannel channel) {
try {
channel.close();
} catch (IOException ignored) {
-
+ // no-op
}
}
}
diff --git a/src/main/java/org/tarantool/TarantoolClientConfig.java b/src/main/java/org/tarantool/TarantoolClientConfig.java
index b83996fc..6ddc40fb 100644
--- a/src/main/java/org/tarantool/TarantoolClientConfig.java
+++ b/src/main/java/org/tarantool/TarantoolClientConfig.java
@@ -1,53 +1,70 @@
package org.tarantool;
-
-import java.util.concurrent.TimeUnit;
-
public class TarantoolClientConfig {
/**
- * username and password for authorization
+ * Auth-related data.
*/
public String username;
-
public String password;
/**
- * default ByteArrayOutputStream size when make query serialization
+ * Default request size when make query serialization.
*/
public int defaultRequestSize = 4096;
/**
- * initial size for map which holds futures of sent request
+ * Initial capacity for the map which holds futures of sent request.
*/
public int predictedFutures = (int) ((1024 * 1024) / 0.75) + 1;
-
public int writerThreadPriority = Thread.NORM_PRIORITY;
-
public int readerThreadPriority = Thread.NORM_PRIORITY;
-
/**
- * shared buffer is place where client collect requests when socket is busy on write
+ * Shared buffer size (place where client collects requests
+ * when socket is busy on write).
*/
public int sharedBufferSize = 8 * 1024 * 1024;
+
/**
- * not put request into the shared buffer if request size is ge directWriteFactor * sharedBufferSize
+ * Factor to calculate a threshold whether request will be accommodated
+ * in the shared buffer.
+ *
+ * if request size exceeds directWriteFactor * sharedBufferSize
+ * request is sent directly.
*/
public double directWriteFactor = 0.5d;
/**
- * Use old call command https://github.com/tarantool/doc/issues/54,
- * please ensure that you server supports new call command
+ * Write operation timeout.
+ */
+ public long writeTimeoutMillis = 60 * 1000L;
+
+ /**
+ * Use old call command https://github.com/tarantool/doc/issues/54,
+ * please ensure that you server supports new call command.
*/
public boolean useNewCall = false;
/**
- * Any blocking ops timeout
+ * Max time to establish connection to the server
+ * and be completely configured (to have an {@code ALIVE} status).
+ *
+ * @see TarantoolClient#isAlive()
*/
- public long initTimeoutMillis = 60*1000L;
+ public long initTimeoutMillis = 60 * 1000L;
- public long writeTimeoutMillis = 60*1000L;
+ /**
+ * Connection timeout per attempt.
+ * {@code 0} means no timeout.
+ */
+ public int connectionTimeout = 2 * 1000;
+
+ /**
+ * Total attempts number to connect to DB.
+ * {@code 0} means unlimited attempts.
+ */
+ public int retryCount = 3;
}
diff --git a/src/main/java/org/tarantool/TarantoolClientImpl.java b/src/main/java/org/tarantool/TarantoolClientImpl.java
index bd7a993d..7c53cfeb 100644
--- a/src/main/java/org/tarantool/TarantoolClientImpl.java
+++ b/src/main/java/org/tarantool/TarantoolClientImpl.java
@@ -2,8 +2,8 @@
import org.tarantool.protocol.ProtoUtils;
import org.tarantool.protocol.ReadableViaSelectorChannel;
-import org.tarantool.protocol.TarantoolPacket;
import org.tarantool.protocol.TarantoolGreeting;
+import org.tarantool.protocol.TarantoolPacket;
import java.io.IOException;
import java.nio.ByteBuffer;
@@ -22,44 +22,47 @@
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Condition;
-import java.util.concurrent.locks.LockSupport;
import java.util.concurrent.locks.ReentrantLock;
-
public class TarantoolClientImpl extends TarantoolBase> implements TarantoolClient {
- public static final CommunicationException NOT_INIT_EXCEPTION = new CommunicationException("Not connected, initializing connection");
+
+ public static final CommunicationException NOT_INIT_EXCEPTION
+ = new CommunicationException("Not connected, initializing connection");
+
protected TarantoolClientConfig config;
/**
- * External
+ * External.
*/
protected SocketChannelProvider socketProvider;
+ protected SocketChannel channel;
+ protected ReadableViaSelectorChannel readChannel;
+
protected volatile Exception thumbstone;
- protected Map> futures;
- protected AtomicInteger wait = new AtomicInteger();
+ protected Map> futures;
+ protected AtomicInteger pendingResponsesCount = new AtomicInteger();
+
/**
- * Write properties
+ * Write properties.
*/
- protected SocketChannel channel;
- protected ReadableViaSelectorChannel readChannel;
-
protected ByteBuffer sharedBuffer;
- protected ByteBuffer writerBuffer;
protected ReentrantLock bufferLock = new ReentrantLock(false);
protected Condition bufferNotEmpty = bufferLock.newCondition();
protected Condition bufferEmpty = bufferLock.newCondition();
+
+ protected ByteBuffer writerBuffer;
protected ReentrantLock writeLock = new ReentrantLock(true);
/**
- * Interfaces
+ * Interfaces.
*/
protected SyncOps syncOps;
protected FireAndForgetOps fireAndForgetOps;
protected ComposableAsyncOps composableAsyncOps;
/**
- * Inner
+ * Inner.
*/
protected TarantoolClientStats stats;
protected StateHelper state = new StateHelper(StateHelper.RECONNECT);
@@ -70,16 +73,31 @@ public class TarantoolClientImpl extends TarantoolBase> implements Tar
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
- if (state.compareAndSet(StateHelper.RECONNECT, 0)) {
- reconnect(0, thumbstone);
+ reconnect(thumbstone);
+ try {
+ state.awaitReconnection();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
}
- LockSupport.park(state);
}
}
});
+ public TarantoolClientImpl(String address, TarantoolClientConfig config) {
+ this(new SingleSocketChannelProviderImpl(address), config);
+ }
+
public TarantoolClientImpl(SocketChannelProvider socketProvider, TarantoolClientConfig config) {
- super();
+ initClient(socketProvider, config);
+ if (socketProvider instanceof ConfigurableSocketChannelProvider) {
+ ConfigurableSocketChannelProvider configurableProvider = (ConfigurableSocketChannelProvider) socketProvider;
+ configurableProvider.setConnectionTimeout(config.connectionTimeout);
+ configurableProvider.setRetriesLimit(config.retryCount);
+ }
+ startConnector(config.initTimeoutMillis);
+ }
+
+ private void initClient(SocketChannelProvider socketProvider, TarantoolClientConfig config) {
this.thumbstone = NOT_INIT_EXCEPTION;
this.config = config;
this.initialRequestSize = config.defaultRequestSize;
@@ -99,12 +117,17 @@ public TarantoolClientImpl(SocketChannelProvider socketProvider, TarantoolClient
this.fireAndForgetOps.setCallCode(Code.CALL);
this.composableAsyncOps.setCallCode(Code.CALL);
}
+ }
+
+ private void startConnector(long initTimeoutMillis) {
connector.start();
try {
- if (!waitAlive(config.initTimeoutMillis, TimeUnit.MILLISECONDS)) {
- CommunicationException e = new CommunicationException(config.initTimeoutMillis +
+ if (!waitAlive(initTimeoutMillis, TimeUnit.MILLISECONDS)) {
+ CommunicationException e = new CommunicationException(
+ initTimeoutMillis +
"ms is exceeded when waiting for client initialization. " +
- "You could configure init timeout in TarantoolConfig");
+ "You could configure init timeout in TarantoolConfig"
+ );
close(e);
throw e;
@@ -115,40 +138,44 @@ public TarantoolClientImpl(SocketChannelProvider socketProvider, TarantoolClient
}
}
- protected void reconnect(int retry, Throwable lastError) {
- SocketChannel channel;
+ protected void reconnect(Throwable lastError) {
+ SocketChannel channel = null;
+ int retryNumber = 0;
while (!Thread.currentThread().isInterrupted()) {
try {
- channel = socketProvider.get(retry++, lastError == NOT_INIT_EXCEPTION ? null : lastError);
+ channel = socketProvider.get(retryNumber++, lastError == NOT_INIT_EXCEPTION ? null : lastError);
} catch (Exception e) {
- close(e);
- return;
+ closeChannel(channel);
+ lastError = e;
+ if (!(e instanceof SocketProviderTransientException)) {
+ close(e);
+ return;
+ }
}
try {
- connect(channel);
- return;
+ if (channel != null) {
+ connect(channel);
+ return;
+ }
} catch (Exception e) {
closeChannel(channel);
lastError = e;
- if (e instanceof InterruptedException)
+ if (e instanceof InterruptedException) {
Thread.currentThread().interrupt();
+ }
}
}
}
protected void connect(final SocketChannel channel) throws Exception {
try {
- TarantoolGreeting greeting = ProtoUtils.connect(channel,
- config.username, config.password);
+ TarantoolGreeting greeting = ProtoUtils.connect(channel, config.username, config.password);
this.serverVersion = greeting.getServerVersion();
} catch (IOException e) {
- try {
- channel.close();
- } catch (IOException ignored) {
- }
-
+ closeChannel(channel);
throw new CommunicationException("Couldn't connect to tarantool", e);
}
+
channel.configureBlocking(false);
this.channel = channel;
this.readChannel = new ReadableViaSelectorChannel(channel);
@@ -164,42 +191,42 @@ protected void connect(final SocketChannel channel) throws Exception {
}
protected void startThreads(String threadName) throws InterruptedException {
- final CountDownLatch init = new CountDownLatch(2);
- reader = new Thread(new Runnable() {
- @Override
- public void run() {
- init.countDown();
- if (state.acquire(StateHelper.READING)) {
- try {
- readThread();
- } finally {
- state.release(StateHelper.READING);
- if (state.compareAndSet(0, StateHelper.RECONNECT))
- LockSupport.unpark(connector);
+ final CountDownLatch ioThreadStarted = new CountDownLatch(2);
+ final AtomicInteger leftIoThreads = new AtomicInteger(2);
+ reader = new Thread(() -> {
+ ioThreadStarted.countDown();
+ if (state.acquire(StateHelper.READING)) {
+ try {
+ readThread();
+ } finally {
+ state.release(StateHelper.READING);
+ // only last of two IO-threads can signal for reconnection
+ if (leftIoThreads.decrementAndGet() == 0) {
+ state.trySignalForReconnection();
}
}
}
});
- writer = new Thread(new Runnable() {
- @Override
- public void run() {
- init.countDown();
- if (state.acquire(StateHelper.WRITING)) {
- try {
- writeThread();
- } finally {
- state.release(StateHelper.WRITING);
- if (state.compareAndSet(0, StateHelper.RECONNECT))
- LockSupport.unpark(connector);
+ writer = new Thread(() -> {
+ ioThreadStarted.countDown();
+ if (state.acquire(StateHelper.WRITING)) {
+ try {
+ writeThread();
+ } finally {
+ state.release(StateHelper.WRITING);
+ // only last of two IO-threads can signal for reconnection
+ if (leftIoThreads.decrementAndGet() == 0) {
+ state.trySignalForReconnection();
}
}
}
});
+ state.release(StateHelper.RECONNECT);
configureThreads(threadName);
reader.start();
writer.start();
- init.await();
+ ioThreadStarted.await();
}
protected void configureThreads(String threadName) {
@@ -216,42 +243,43 @@ protected Future> exec(Code code, Object... args) {
protected CompletableFuture> doExec(Code code, Object[] args) {
validateArgs(args);
long sid = syncId.incrementAndGet();
- CompletableFuture> q = new CompletableFuture<>();
+ TarantoolOp> future = new TarantoolOp<>(code);
- if (isDead(q)) {
- return q;
+ if (isDead(future)) {
+ return future;
}
- futures.put(sid, q);
- if (isDead(q)) {
+ futures.put(sid, future);
+ if (isDead(future)) {
futures.remove(sid);
- return q;
+ return future;
}
try {
write(code, sid, null, args);
} catch (Exception e) {
futures.remove(sid);
- fail(q, e);
+ fail(future, e);
}
- return q;
+ return future;
}
protected synchronized void die(String message, Exception cause) {
if (thumbstone != null) {
return;
}
- final CommunicationException err = new CommunicationException(message, cause);
- this.thumbstone = err;
+ final CommunicationException error = new CommunicationException(message, cause);
+ this.thumbstone = error;
while (!futures.isEmpty()) {
- Iterator>> iterator = futures.entrySet().iterator();
+ Iterator>> iterator = futures.entrySet().iterator();
while (iterator.hasNext()) {
- Map.Entry> elem = iterator.next();
+ Map.Entry> elem = iterator.next();
if (elem != null) {
- CompletableFuture> future = elem.getValue();
- fail(future, err);
+ TarantoolOp> future = elem.getValue();
+ fail(future, error);
}
iterator.remove();
}
}
+ pendingResponsesCount.set(0);
bufferLock.lock();
try {
@@ -268,7 +296,7 @@ public void ping() {
}
protected void write(Code code, Long syncId, Long schemaId, Object... args)
- throws Exception {
+ throws Exception {
ByteBuffer buffer = ProtoUtils.createPacket(code, syncId, schemaId, args);
if (directWrite(buffer)) {
@@ -293,14 +321,18 @@ protected void sharedWrite(ByteBuffer buffer) throws InterruptedException, Timeo
try {
if (remaining < 1 || !bufferEmpty.await(remaining, TimeUnit.MILLISECONDS)) {
stats.sharedEmptyAwaitTimeouts++;
- throw new TimeoutException(config.writeTimeoutMillis + "ms is exceeded while waiting for empty buffer you could configure write timeout it in TarantoolConfig");
+ throw new TimeoutException(
+ config.writeTimeoutMillis +
+ "ms is exceeded while waiting for empty buffer. " +
+ "You could configure write timeout it in TarantoolConfig"
+ );
}
} catch (InterruptedException e) {
throw new CommunicationException("Interrupted", e);
}
}
sharedBuffer.put(buffer);
- wait.incrementAndGet();
+ pendingResponsesCount.incrementAndGet();
bufferNotEmpty.signalAll();
stats.buffered++;
} finally {
@@ -308,7 +340,11 @@ protected void sharedWrite(ByteBuffer buffer) throws InterruptedException, Timeo
}
} else {
stats.sharedWriteLockTimeouts++;
- throw new TimeoutException(config.writeTimeoutMillis + "ms is exceeded while waiting for shared buffer lock you could configure write timeout in TarantoolConfig");
+ throw new TimeoutException(
+ config.writeTimeoutMillis +
+ "ms is exceeded while waiting for shared buffer lock. " +
+ "You could configure write timeout in TarantoolConfig"
+ );
}
}
@@ -323,39 +359,39 @@ private boolean directWrite(ByteBuffer buffer) throws InterruptedException, IOEx
}
writeFully(channel, buffer);
stats.directWrite++;
- wait.incrementAndGet();
+ pendingResponsesCount.incrementAndGet();
} finally {
writeLock.unlock();
}
return true;
} else {
stats.directWriteLockTimeouts++;
- throw new TimeoutException(config.writeTimeoutMillis + "ms is exceeded while waiting for channel lock you could configure write timeout in TarantoolConfig");
+ throw new TimeoutException(
+ config.writeTimeoutMillis +
+ "ms is exceeded while waiting for channel lock. " +
+ "You could configure write timeout in TarantoolConfig"
+ );
}
}
return false;
}
protected void readThread() {
- try {
- while (!Thread.currentThread().isInterrupted()) {
- try {
- TarantoolPacket packet = ProtoUtils.readPacket(readChannel);
+ while (!Thread.currentThread().isInterrupted()) {
+ try {
+ TarantoolPacket packet = ProtoUtils.readPacket(readChannel);
- Map headers = packet.getHeaders();
+ Map headers = packet.getHeaders();
- Long syncId = (Long) headers.get(Key.SYNC.getId());
- CompletableFuture> future = futures.remove(syncId);
- stats.received++;
- wait.decrementAndGet();
- complete(packet, future);
- } catch (Exception e) {
- die("Cant read answer", e);
- return;
- }
+ Long syncId = (Long) headers.get(Key.SYNC.getId());
+ TarantoolOp> future = futures.remove(syncId);
+ stats.received++;
+ pendingResponsesCount.decrementAndGet();
+ complete(packet, future);
+ } catch (Exception e) {
+ die("Cant read answer", e);
+ return;
}
- } catch (Exception e) {
- die("Cant init thread", e);
}
}
@@ -395,36 +431,35 @@ protected void fail(CompletableFuture> q, Exception e) {
q.completeExceptionally(e);
}
- protected void complete(TarantoolPacket packet, CompletableFuture> q) {
- if (q != null) {
+ protected void complete(TarantoolPacket packet, TarantoolOp> future) {
+ if (future != null) {
long code = packet.getCode();
if (code == 0) {
-
- if (code == Code.EXECUTE.getId()) {
- completeSql(q, packet);
+ if (future.getCode() == Code.EXECUTE) {
+ completeSql(future, packet);
} else {
- ((CompletableFuture) q).complete(packet.getBody().get(Key.DATA.getId()));
+ ((CompletableFuture) future).complete(packet.getBody().get(Key.DATA.getId()));
}
} else {
Object error = packet.getBody().get(Key.ERROR.getId());
- fail(q, serverError(code, error));
+ fail(future, serverError(code, error));
}
}
}
- protected void completeSql(CompletableFuture> q, TarantoolPacket pack) {
+ protected void completeSql(CompletableFuture> future, TarantoolPacket pack) {
Long rowCount = SqlProtoUtils.getSqlRowCount(pack);
- if (rowCount!=null) {
- ((CompletableFuture) q).complete(rowCount);
+ if (rowCount != null) {
+ ((CompletableFuture) future).complete(rowCount);
} else {
List> values = SqlProtoUtils.readSqlResult(pack);
- ((CompletableFuture) q).complete(values);
+ ((CompletableFuture) future).complete(values);
}
}
- protected T syncGet(Future r) {
+ protected T syncGet(Future result) {
try {
- return r.get();
+ return result.get();
} catch (ExecutionException e) {
if (e.getCause() instanceof CommunicationException) {
throw (CommunicationException) e.getCause();
@@ -455,7 +490,6 @@ public void close() {
protected void close(Exception e) {
if (state.close()) {
connector.interrupt();
-
die(e.getMessage(), e);
}
}
@@ -469,9 +503,9 @@ protected void stopIO() {
}
if (readChannel != null) {
try {
- readChannel.close();//also closes this.channel
+ readChannel.close(); // also closes this.channel
} catch (IOException ignored) {
-
+ // no-op
}
}
closeChannel(channel);
@@ -499,7 +533,7 @@ public TarantoolClientOps, Object, List>> syncOps() {
@Override
public TarantoolClientOps, Object, Future>> asyncOps() {
- return (TarantoolClientOps)this;
+ return (TarantoolClientOps) this;
}
@Override
@@ -515,7 +549,7 @@ public TarantoolClientOps, Object, Long> fireAndForgetOps() {
@Override
public TarantoolSQLOps>> sqlSyncOps() {
- return new TarantoolSQLOps>>() {
+ return new TarantoolSQLOps>>() {
@Override
public Long update(String sql, Object... bind) {
@@ -531,7 +565,7 @@ public List> query(String sql, Object... bind) {
@Override
public TarantoolSQLOps, Future>>> sqlAsyncOps() {
- return new TarantoolSQLOps, Future>>>() {
+ return new TarantoolSQLOps, Future>>>() {
@Override
public Future update(String sql, Object... bind) {
return (Future) exec(Code.EXECUTE, Key.SQL_TEXT, sql, Key.SQL_BIND, bind);
@@ -555,9 +589,11 @@ public List exec(Code code, Object... args) {
public void close() {
throw new IllegalStateException("You should close TarantoolClient instead.");
}
+
}
protected class FireAndForgetOps extends AbstractTarantoolOps, Object, Long> {
+
@Override
public Long exec(Code code, Object... args) {
if (thumbstone == null) {
@@ -577,9 +613,12 @@ public Long exec(Code code, Object... args) {
public void close() {
throw new IllegalStateException("You should close TarantoolClient instead.");
}
+
}
- protected class ComposableAsyncOps extends AbstractTarantoolOps, Object, CompletionStage>> {
+ protected class ComposableAsyncOps
+ extends AbstractTarantoolOps, Object, CompletionStage>> {
+
@Override
public CompletionStage> exec(Code code, Object... args) {
return (CompletionStage>) TarantoolClientImpl.this.doExec(code, args);
@@ -589,10 +628,11 @@ public CompletionStage> exec(Code code, Object... args) {
public void close() {
TarantoolClientImpl.this.close();
}
+
}
protected boolean isDead(CompletableFuture> q) {
- if (TarantoolClientImpl.this.thumbstone != null) {
+ if (this.thumbstone != null) {
fail(q, new CommunicationException("Connection is dead", thumbstone));
return true;
}
@@ -619,6 +659,8 @@ public TarantoolClientStats getStats() {
* Manages state changes.
*/
protected final class StateHelper {
+
+ static final int UNINITIALIZED = 0;
static final int READING = 1;
static final int WRITING = 2;
static final int ALIVE = READING | WRITING;
@@ -628,10 +670,22 @@ protected final class StateHelper {
private final AtomicInteger state;
private final AtomicReference nextAliveLatch =
- new AtomicReference(new CountDownLatch(1));
+ new AtomicReference<>(new CountDownLatch(1));
private final CountDownLatch closedLatch = new CountDownLatch(1);
+ /**
+ * The condition variable to signal a reconnection is needed from reader /
+ * writer threads and waiting for that signal from the reconnection thread.
+ *
+ * The lock variable to access this condition.
+ *
+ * @see #awaitReconnection()
+ * @see #trySignalForReconnection()
+ */
+ protected final ReentrantLock connectorLock = new ReentrantLock();
+ protected final Condition reconnectRequired = connectorLock.newCondition();
+
protected StateHelper(int state) {
this.state = new AtomicInteger(state);
}
@@ -640,35 +694,62 @@ protected int getState() {
return state.get();
}
+ /**
+ * Set CLOSED state, drop RECONNECT state.
+ */
protected boolean close() {
- for (;;) {
- int st = getState();
- if ((st & CLOSED) == CLOSED)
+ for (; ; ) {
+ int currentState = getState();
+
+ /* CLOSED is the terminal state. */
+ if ((currentState & CLOSED) == CLOSED) {
return false;
- if (compareAndSet(st, (st & ~RECONNECT) | CLOSED))
+ }
+
+ /* Drop RECONNECT, set CLOSED. */
+ if (compareAndSet(currentState, (currentState & ~RECONNECT) | CLOSED)) {
return true;
+ }
}
}
+ /**
+ * Move from a current state to a give one.
+ *
+ * Some moves are forbidden.
+ */
protected boolean acquire(int mask) {
- for (;;) {
- int st = getState();
- if ((st & CLOSED) == CLOSED)
+ for (; ; ) {
+ int currentState = getState();
+
+ /* CLOSED is the terminal state. */
+ if ((currentState & CLOSED) == CLOSED) {
return false;
+ }
+
+ /* Don't move to READING, WRITING or ALIVE from RECONNECT. */
+ if ((currentState & RECONNECT) > mask) {
+ return false;
+ }
- if ((st & mask) != 0)
+ /* Cannot move from a state to the same state. */
+ if ((currentState & mask) != 0) {
throw new IllegalStateException("State is already " + mask);
+ }
- if (compareAndSet(st, st | mask))
+ /* Set acquired state. */
+ if (compareAndSet(currentState, currentState | mask)) {
return true;
+ }
}
}
protected void release(int mask) {
- for (;;) {
- int st = getState();
- if (compareAndSet(st, st & ~mask))
+ for (; ; ) {
+ int currentState = getState();
+ if (compareAndSet(currentState, currentState & ~mask)) {
return;
+ }
}
}
@@ -687,10 +768,18 @@ protected boolean compareAndSet(int expect, int update) {
return true;
}
+ /**
+ * Reconnection uses another way to await state via receiving a signal
+ * instead of latches.
+ */
protected void awaitState(int state) throws InterruptedException {
- CountDownLatch latch = getStateLatch(state);
- if (latch != null) {
- latch.await();
+ if (state == RECONNECT) {
+ awaitReconnection();
+ } else {
+ CountDownLatch latch = getStateLatch(state);
+ if (latch != null) {
+ latch.await();
+ }
}
}
@@ -710,9 +799,60 @@ private CountDownLatch getStateLatch(int state) {
CountDownLatch latch = nextAliveLatch.get();
/* It may happen so that an error is detected but the state is still alive.
Wait for the 'next' alive state in such cases. */
- return (getState() == ALIVE && thumbstone == null) ? null : latch;
+ return (getState() == ALIVE && thumbstone == null) ? null : latch;
}
return null;
}
+
+ /**
+ * Blocks until a reconnection signal will be received.
+ *
+ * @see #trySignalForReconnection()
+ */
+ private void awaitReconnection() throws InterruptedException {
+ connectorLock.lock();
+ try {
+ while (getState() != StateHelper.RECONNECT) {
+ reconnectRequired.await();
+ }
+ } finally {
+ connectorLock.unlock();
+ }
+ }
+
+ /**
+ * Signals to the connector that reconnection process can be performed.
+ *
+ * @see #awaitReconnection()
+ */
+ private void trySignalForReconnection() {
+ if (compareAndSet(StateHelper.UNINITIALIZED, StateHelper.RECONNECT)) {
+ connectorLock.lock();
+ try {
+ reconnectRequired.signal();
+ } finally {
+ connectorLock.unlock();
+ }
+ }
+ }
+
}
+
+ protected static class TarantoolOp extends CompletableFuture {
+
+ /**
+ * Tarantool binary protocol operation code.
+ */
+ private final Code code;
+
+ public TarantoolOp(Code code) {
+ this.code = code;
+ }
+
+ public Code getCode() {
+ return code;
+ }
+
+ }
+
}
diff --git a/src/main/java/org/tarantool/TarantoolClusterClient.java b/src/main/java/org/tarantool/TarantoolClusterClient.java
index 3a6c243a..7b71f10e 100644
--- a/src/main/java/org/tarantool/TarantoolClusterClient.java
+++ b/src/main/java/org/tarantool/TarantoolClusterClient.java
@@ -1,50 +1,95 @@
package org.tarantool;
+import org.tarantool.cluster.TarantoolClusterDiscoverer;
+import org.tarantool.cluster.TarantoolClusterStoredFunctionDiscoverer;
+import org.tarantool.protocol.TarantoolPacket;
+import org.tarantool.util.StringUtils;
+
+import java.io.IOException;
+import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
-
-import static org.tarantool.TarantoolClientImpl.StateHelper.CLOSED;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.StampedLock;
/**
* Basic implementation of a client that may work with the cluster
* of tarantool instances in fault-tolerant way.
- *
+ *
* Failed operations will be retried once connection is re-established
* unless the configured expiration time is over.
*/
public class TarantoolClusterClient extends TarantoolClientImpl {
- /* Need some execution context to retry writes. */
+
+ /**
+ * Need some execution context to retry writes.
+ */
private Executor executor;
- /* Collection of operations to be retried. */
- private ConcurrentHashMap> retries = new ConcurrentHashMap>();
+ /**
+ * Discovery activity.
+ */
+ private ScheduledExecutorService instancesDiscoveryExecutor;
+ private Runnable instancesDiscovererTask;
+ private StampedLock discoveryLock = new StampedLock();
+
+ /**
+ * Collection of operations to be retried.
+ */
+ private ConcurrentHashMap> retries = new ConcurrentHashMap<>();
/**
- * @param config Configuration.
- * @param addrs Array of addresses in the form of [host]:[port].
+ * Constructs a new cluster client.
+ *
+ * @param config Configuration.
+ * @param addresses Array of addresses in the form of host[:port].
*/
- public TarantoolClusterClient(TarantoolClusterClientConfig config, String... addrs) {
- this(config, new RoundRobinSocketProviderImpl(addrs).setTimeout(config.operationExpiryTimeMillis));
+ public TarantoolClusterClient(TarantoolClusterClientConfig config, String... addresses) {
+ this(config, makeClusterSocketProvider(addresses));
}
/**
+ * Constructs a new cluster client.
+ *
* @param provider Socket channel provider.
- * @param config Configuration.
+ * @param config Configuration.
*/
public TarantoolClusterClient(TarantoolClusterClientConfig config, SocketChannelProvider provider) {
super(provider, config);
- this.executor = config.executor == null ?
- Executors.newSingleThreadExecutor() : config.executor;
+ this.executor = config.executor == null
+ ? Executors.newSingleThreadExecutor()
+ : config.executor;
+
+ if (StringUtils.isNotBlank(config.clusterDiscoveryEntryFunction)) {
+ this.instancesDiscovererTask =
+ createDiscoveryTask(new TarantoolClusterStoredFunctionDiscoverer(config, this));
+ this.instancesDiscoveryExecutor
+ = Executors.newSingleThreadScheduledExecutor(new TarantoolThreadDaemonFactory("tarantoolDiscoverer"));
+ int delay = config.clusterDiscoveryDelayMillis > 0
+ ? config.clusterDiscoveryDelayMillis
+ : TarantoolClusterClientConfig.DEFAULT_CLUSTER_DISCOVERY_DELAY_MILLIS;
+
+ // todo: it's better to start a job later (out of ctor)
+ this.instancesDiscoveryExecutor.scheduleWithFixedDelay(
+ this.instancesDiscovererTask,
+ 0,
+ delay,
+ TimeUnit.MILLISECONDS
+ );
+ }
}
@Override
protected boolean isDead(CompletableFuture> q) {
- if ((state.getState() & CLOSED) != 0) {
+ if ((state.getState() & StateHelper.CLOSED) != 0) {
q.completeExceptionally(new CommunicationException("Connection is dead", thumbstone));
return true;
}
@@ -59,23 +104,42 @@ protected boolean isDead(CompletableFuture> q) {
protected CompletableFuture> doExec(Code code, Object[] args) {
validateArgs(args);
long sid = syncId.incrementAndGet();
- CompletableFuture> q = makeFuture(sid, code, args);
+ ExpirableOp> future = makeFuture(sid, code, args);
+ return registerOperation(future);
+ }
- if (isDead(q)) {
- return q;
- }
- futures.put(sid, q);
- if (isDead(q)) {
- futures.remove(sid);
- return q;
- }
+ /**
+ * Registers a new async operation which will be resolved later.
+ * Registration is discovery-aware in term of synchronization and
+ * it may be blocked util the discovery finishes its work.
+ *
+ * @param future operation to be performed
+ *
+ * @return registered operation
+ */
+ private CompletableFuture> registerOperation(ExpirableOp> future) {
+ long stamp = discoveryLock.readLock();
try {
- write(code, sid, null, args);
- } catch (Exception e) {
- futures.remove(sid);
- fail(q, e);
+ if (isDead(future)) {
+ return future;
+ }
+ futures.put(future.getId(), future);
+ if (isDead(future)) {
+ futures.remove(future.getId());
+ return future;
+ }
+
+ try {
+ write(future.getCode(), future.getId(), null, future.getArgs());
+ } catch (Exception e) {
+ futures.remove(future.getId());
+ fail(future, e);
+ }
+
+ return future;
+ } finally {
+ discoveryLock.unlock(stamp);
}
- return q;
}
@Override
@@ -85,12 +149,12 @@ protected void fail(CompletableFuture> q, Exception e) {
protected boolean checkFail(CompletableFuture> q, Exception e) {
assert q instanceof ExpirableOp>;
- if (!isTransientError(e) || ((ExpirableOp>)q).hasExpired(System.currentTimeMillis())) {
+ if (!isTransientError(e) || ((ExpirableOp>) q).hasExpired(System.currentTimeMillis())) {
q.completeExceptionally(e);
return true;
} else {
assert retries != null;
- retries.put(((ExpirableOp>) q).getId(), (ExpirableOp>)q);
+ retries.put(((ExpirableOp>) q).getId(), (ExpirableOp>) q);
return false;
}
}
@@ -99,6 +163,10 @@ protected boolean checkFail(CompletableFuture> q, Exception e) {
protected void close(Exception e) {
super.close(e);
+ if (instancesDiscoveryExecutor != null) {
+ instancesDiscoveryExecutor.shutdownNow();
+ }
+
if (retries == null) {
// May happen within constructor.
return;
@@ -114,12 +182,12 @@ protected boolean isTransientError(Exception e) {
return true;
}
if (e instanceof TarantoolException) {
- return ((TarantoolException)e).isTransient();
+ return ((TarantoolException) e).isTransient();
}
return false;
}
- protected CompletableFuture> makeFuture(long id, Code code, Object...args) {
+ private ExpirableOp> makeFuture(long id, Code code, Object... args) {
int expireTime = ((TarantoolClusterClientConfig) config).operationExpiryTimeMillis;
return new ExpirableOp(id, expireTime, code, args);
}
@@ -133,58 +201,129 @@ protected void onReconnect() {
// First call is before the constructor finished. Skip it.
return;
}
- Collection> futsToRetry = new ArrayList>(retries.values());
+ Collection> futuresToRetry = new ArrayList<>(retries.values());
retries.clear();
long now = System.currentTimeMillis();
- for (final ExpirableOp> fut : futsToRetry) {
- if (!fut.hasExpired(now)) {
- executor.execute(new Runnable() {
- @Override
- public void run() {
- futures.put(fut.getId(), fut);
- try {
- write(fut.getCode(), fut.getId(), null, fut.getArgs());
- } catch (Exception e) {
- futures.remove(fut.getId());
- fail(fut, e);
- }
- }
- });
+ for (final ExpirableOp> future : futuresToRetry) {
+ if (!future.hasExpired(now)) {
+ executor.execute(() -> registerOperation(future));
+ }
+ }
+ }
+
+ @Override
+ protected void complete(TarantoolPacket packet, TarantoolOp> future) {
+ super.complete(packet, future);
+ RefreshableSocketProvider provider = getRefreshableSocketProvider();
+ if (provider != null) {
+ renewConnectionIfRequired(provider.getAddresses());
+ }
+ }
+
+ protected void onInstancesRefreshed(Set instances) {
+ RefreshableSocketProvider provider = getRefreshableSocketProvider();
+ if (provider != null) {
+ provider.refreshAddresses(instances);
+ renewConnectionIfRequired(provider.getAddresses());
+ }
+ }
+
+ private RefreshableSocketProvider getRefreshableSocketProvider() {
+ return socketProvider instanceof RefreshableSocketProvider
+ ? (RefreshableSocketProvider) socketProvider
+ : null;
+ }
+
+ private void renewConnectionIfRequired(Collection addresses) {
+ if (pendingResponsesCount.get() > 0 || !isAlive()) {
+ return;
+ }
+ SocketAddress addressInUse = getCurrentAddressOrNull();
+ if (!(addressInUse == null || addresses.contains(addressInUse))) {
+ long stamp = discoveryLock.tryWriteLock();
+ if (!discoveryLock.validate(stamp)) {
+ return;
+ }
+ try {
+ if (pendingResponsesCount.get() == 0) {
+ stopIO();
+ }
+ } finally {
+ discoveryLock.unlock(stamp);
}
}
}
+ private SocketAddress getCurrentAddressOrNull() {
+ try {
+ return channel.getRemoteAddress();
+ } catch (IOException ignored) {
+ return null;
+ }
+ }
+
+ public void refreshInstances() {
+ if (instancesDiscovererTask != null) {
+ instancesDiscovererTask.run();
+ }
+ }
+
+ private static RoundRobinSocketProviderImpl makeClusterSocketProvider(String[] addresses) {
+ return new RoundRobinSocketProviderImpl(addresses);
+ }
+
+ private Runnable createDiscoveryTask(TarantoolClusterDiscoverer serviceDiscoverer) {
+ return new Runnable() {
+
+ private Set lastInstances;
+
+ @Override
+ public synchronized void run() {
+ try {
+ Set freshInstances = serviceDiscoverer.getInstances();
+ if (!(freshInstances.isEmpty() || Objects.equals(lastInstances, freshInstances))) {
+ lastInstances = freshInstances;
+ onInstancesRefreshed(lastInstances);
+ }
+ } catch (Exception ignored) {
+ // no-op
+ }
+ }
+ };
+ }
+
/**
* Holds operation code and arguments for retry.
*/
- private class ExpirableOp extends CompletableFuture {
- /** Moment in time when operation is not considered for retry. */
- final private long deadline;
+ private class ExpirableOp extends TarantoolOp {
/**
- * A task identifier used in {@link TarantoolClientImpl#futures}.
+ * Moment in time when operation is not considered for retry.
*/
- final private long id;
+ private final long deadline;
/**
- * Tarantool binary protocol operation code.
+ * A task identifier used in {@link TarantoolClientImpl#futures}.
*/
- final private Code code;
+ private final long id;
- /** Arguments of operation. */
- final private Object[] args;
+ /**
+ * Arguments of operation.
+ */
+ private final Object[] args;
/**
+ * Constructs a new Expirable operation.
*
- * @param id Sync.
+ * @param id Sync.
* @param expireTime Expiration time (relative) in ms.
- * @param code Tarantool operation code.
- * @param args Operation arguments.
+ * @param code Tarantool operation code.
+ * @param args Operation arguments.
*/
- ExpirableOp(long id, int expireTime, Code code, Object...args) {
+ ExpirableOp(long id, int expireTime, Code code, Object... args) {
+ super(code);
this.id = id;
this.deadline = System.currentTimeMillis() + expireTime;
- this.code = code;
this.args = args;
}
@@ -196,12 +335,9 @@ public long getId() {
return id;
}
- public Code getCode() {
- return code;
- }
-
public Object[] getArgs() {
return args;
}
}
+
}
diff --git a/src/main/java/org/tarantool/TarantoolClusterClientConfig.java b/src/main/java/org/tarantool/TarantoolClusterClientConfig.java
index 423896b3..81f67cbb 100644
--- a/src/main/java/org/tarantool/TarantoolClusterClientConfig.java
+++ b/src/main/java/org/tarantool/TarantoolClusterClientConfig.java
@@ -6,9 +6,30 @@
* Configuration for the {@link TarantoolClusterClient}.
*/
public class TarantoolClusterClientConfig extends TarantoolClientConfig {
- /* Amount of time (in milliseconds) the operation is eligible for retry. */
- public int operationExpiryTimeMillis = 500;
- /* Executor service that will be used as a thread of execution to retry writes. */
- public Executor executor = null;
+ public static final int DEFAULT_OPERATION_EXPIRY_TIME_MILLIS = 500;
+ public static final int DEFAULT_CLUSTER_DISCOVERY_DELAY_MILLIS = 60_000;
+
+ /**
+ * Period for the operation is eligible for retry.
+ */
+ public int operationExpiryTimeMillis = DEFAULT_OPERATION_EXPIRY_TIME_MILLIS;
+
+ /**
+ * Executor that will be used as a thread of
+ * execution to retry writes.
+ */
+ public Executor executor;
+
+ /**
+ * Gets a name of the stored function to be used
+ * to fetch list of instances.
+ */
+ public String clusterDiscoveryEntryFunction;
+
+ /**
+ * Scan period for refreshing a new list of instances.
+ */
+ public int clusterDiscoveryDelayMillis = DEFAULT_CLUSTER_DISCOVERY_DELAY_MILLIS;
+
}
diff --git a/src/main/java/org/tarantool/TarantoolConnection.java b/src/main/java/org/tarantool/TarantoolConnection.java
index b51ba0ad..56e5abb7 100644
--- a/src/main/java/org/tarantool/TarantoolConnection.java
+++ b/src/main/java/org/tarantool/TarantoolConnection.java
@@ -12,12 +12,13 @@
import java.util.List;
import java.util.Map;
-public class TarantoolConnection extends TarantoolBase> implements TarantoolSQLOps>> {
+public class TarantoolConnection extends TarantoolBase>
+ implements TarantoolSQLOps>> {
+
protected InputStream in;
protected OutputStream out;
protected Socket socket;
-
public TarantoolConnection(String username, String password, Socket socket) throws IOException {
super(username, password, socket);
this.socket = socket;
@@ -65,11 +66,14 @@ public void rollback() {
call("box.rollback");
}
+ /**
+ * Closes current connection.
+ */
public void close() {
try {
socket.close();
} catch (IOException ignored) {
-
+ // No-op
}
}
diff --git a/src/main/java/org/tarantool/TarantoolException.java b/src/main/java/org/tarantool/TarantoolException.java
index 3778bccf..aed93b14 100644
--- a/src/main/java/org/tarantool/TarantoolException.java
+++ b/src/main/java/org/tarantool/TarantoolException.java
@@ -2,78 +2,69 @@
/**
* A remote server error with error code and message.
- *
+ *
* @author dgreen
* @version $Id: $
*/
public class TarantoolException extends RuntimeException {
- /* taken from src/box/errcode.h */
- public final static int ERR_READONLY = 7;
- public final static int ERR_TIMEOUT = 78;
- public final static int ERR_LOADING = 116;
- public final static int ERR_LOCAL_INSTANCE_ID_IS_READ_ONLY = 128;
+ /* taken from src/box/errcode.h */
+ public static final int ERR_READONLY = 7;
+ public static final int ERR_TIMEOUT = 78;
+ public static final int ERR_LOADING = 116;
+ public static final int ERR_LOCAL_INSTANCE_ID_IS_READ_ONLY = 128;
- private static final long serialVersionUID = 1L;
- long code;
+ private static final long serialVersionUID = 1L;
+ long code;
- /**
- *
- * Getter for the field code
.
- *
- *
- * @return a int.
- */
- public long getCode() {
- return code;
- }
+ /**
+ * Getter for the field code
.
+ *
+ * @return error code
+ */
+ public long getCode() {
+ return code;
+ }
- /**
- *
- * Constructor for TarantoolException.
- *
- *
- * @param code
- * a int.
- * @param message
- * a {@link java.lang.String} object.
- * @param cause
- * a {@link java.lang.Throwable} object.
- */
- public TarantoolException(long code, String message, Throwable cause) {
- super(message, cause);
- this.code = code;
+ /**
+ * Constructor for TarantoolException.
+ *
+ * @param code a int.
+ * @param message a {@link java.lang.String} object.
+ * @param cause a {@link java.lang.Throwable} object.
+ */
+ public TarantoolException(long code, String message, Throwable cause) {
+ super(message, cause);
+ this.code = code;
- }
+ }
- /**
- *
- * Constructor for TarantoolException.
- *
- *
- * @param code
- * a int.
- * @param message
- * a {@link java.lang.String} object.
- */
- public TarantoolException(long code, String message) {
- super(message);
- this.code = code;
+ /**
+ * Constructor for TarantoolException.
+ *
+ * @param code a int.
+ * @param message a {@link java.lang.String} object.
+ */
+ public TarantoolException(long code, String message) {
+ super(message);
+ this.code = code;
- }
+ }
- /**
- *
- * @return {@code true} if retry can possibly help to overcome this error.
- */
- boolean isTransient() {
- switch ((int)code) {
- case ERR_READONLY:
- case ERR_TIMEOUT:
- case ERR_LOADING:
- case ERR_LOCAL_INSTANCE_ID_IS_READ_ONLY:
- return true;
- default:
- return false;
- }
- }
+ /**
+ * Determines whether this error was caused under transient
+ * circumstances or not.
+ *
+ * @return {@code true} if retry can possibly help to overcome this error.
+ */
+ boolean isTransient() {
+ switch ((int) code) {
+ case ERR_READONLY:
+ case ERR_TIMEOUT:
+ case ERR_LOADING:
+ case ERR_LOCAL_INSTANCE_ID_IS_READ_ONLY:
+ return true;
+ default:
+ return false;
+ }
+ }
}
diff --git a/src/main/java/org/tarantool/TarantoolThreadDaemonFactory.java b/src/main/java/org/tarantool/TarantoolThreadDaemonFactory.java
new file mode 100644
index 00000000..f1b4dfb5
--- /dev/null
+++ b/src/main/java/org/tarantool/TarantoolThreadDaemonFactory.java
@@ -0,0 +1,23 @@
+package org.tarantool;
+
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class TarantoolThreadDaemonFactory implements ThreadFactory {
+
+ private static final AtomicInteger POOL_NUMBER = new AtomicInteger(1);
+ private final AtomicInteger threadNumber = new AtomicInteger(1);
+ private final String namePrefix;
+
+ public TarantoolThreadDaemonFactory(String namePrefix) {
+ this.namePrefix = namePrefix + "-" + POOL_NUMBER.incrementAndGet() + "-thread-";
+ }
+
+ @Override
+ public Thread newThread(Runnable runnable) {
+ Thread thread = new Thread(runnable, namePrefix + threadNumber.incrementAndGet());
+ thread.setDaemon(true);
+
+ return thread;
+ }
+}
diff --git a/src/main/java/org/tarantool/cluster/IllegalDiscoveryFunctionResult.java b/src/main/java/org/tarantool/cluster/IllegalDiscoveryFunctionResult.java
new file mode 100644
index 00000000..41f8dd82
--- /dev/null
+++ b/src/main/java/org/tarantool/cluster/IllegalDiscoveryFunctionResult.java
@@ -0,0 +1,13 @@
+package org.tarantool.cluster;
+
+/**
+ * Raised when {@link TarantoolClusterStoredFunctionDiscoverer} validates
+ * a function result as unsupported.
+ */
+public class IllegalDiscoveryFunctionResult extends RuntimeException {
+
+ public IllegalDiscoveryFunctionResult(String message) {
+ super(message);
+ }
+
+}
diff --git a/src/main/java/org/tarantool/cluster/TarantoolClusterDiscoverer.java b/src/main/java/org/tarantool/cluster/TarantoolClusterDiscoverer.java
new file mode 100644
index 00000000..26e5da3c
--- /dev/null
+++ b/src/main/java/org/tarantool/cluster/TarantoolClusterDiscoverer.java
@@ -0,0 +1,21 @@
+package org.tarantool.cluster;
+
+import java.util.Set;
+
+/**
+ * Discovery strategy to obtain a list of the cluster nodes.
+ * This one can be used by {@link org.tarantool.RefreshableSocketProvider}
+ * to provide support for fault tolerance property.
+ *
+ * @see org.tarantool.RefreshableSocketProvider
+ */
+public interface TarantoolClusterDiscoverer {
+
+ /**
+ * Gets nodes addresses in host[:port]
format.
+ *
+ * @return list of the cluster nodes
+ */
+ Set getInstances();
+
+}
diff --git a/src/main/java/org/tarantool/cluster/TarantoolClusterStoredFunctionDiscoverer.java b/src/main/java/org/tarantool/cluster/TarantoolClusterStoredFunctionDiscoverer.java
new file mode 100644
index 00000000..c25b578d
--- /dev/null
+++ b/src/main/java/org/tarantool/cluster/TarantoolClusterStoredFunctionDiscoverer.java
@@ -0,0 +1,63 @@
+package org.tarantool.cluster;
+
+import org.tarantool.TarantoolClient;
+import org.tarantool.TarantoolClientOps;
+import org.tarantool.TarantoolClusterClientConfig;
+
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * A cluster nodes discoverer based on calling a predefined function
+ * which returns list of nodes.
+ *
+ * The function has to have no arguments and return list of
+ * the strings which follow host[:port]
format
+ */
+public class TarantoolClusterStoredFunctionDiscoverer implements TarantoolClusterDiscoverer {
+
+ private TarantoolClient client;
+ private String entryFunction;
+
+ public TarantoolClusterStoredFunctionDiscoverer(TarantoolClusterClientConfig clientConfig, TarantoolClient client) {
+ this.client = client;
+ this.entryFunction = clientConfig.clusterDiscoveryEntryFunction;
+ }
+
+ @Override
+ public Set getInstances() {
+ TarantoolClientOps, Object, List>> syncOperations = client.syncOps();
+
+ List> list = syncOperations.call(entryFunction);
+ // discoverer expects a single array result from the function now;
+ // in order to protect this contract the discoverer does a strict
+ // validation against the data returned;
+ // this strict-mode allows us to extend the contract in a non-breaking
+ // way for old clients just reserve an extra return value in
+ // terms of LUA multi-result support.
+ checkResult(list);
+
+ List funcResult = (List) list.get(0);
+ return funcResult.stream()
+ .map(Object::toString)
+ .collect(Collectors.toCollection(LinkedHashSet::new));
+ }
+
+ /**
+ * Check whether the result follows the contract or not.
+ * The contract is a mandatory single array of strings
+ *
+ * @param result result to be validated
+ */
+ private void checkResult(List> result) {
+ if (result == null || result.isEmpty()) {
+ throw new IllegalDiscoveryFunctionResult("Discovery function returned no data");
+ }
+ if (!((List)result.get(0)).stream().allMatch(item -> item instanceof String)) {
+ throw new IllegalDiscoveryFunctionResult("The first value must be an array of strings");
+ }
+ }
+
+}
diff --git a/src/main/java/org/tarantool/jdbc/SQLConnection.java b/src/main/java/org/tarantool/jdbc/SQLConnection.java
index f6334abe..a8bcd3fb 100644
--- a/src/main/java/org/tarantool/jdbc/SQLConnection.java
+++ b/src/main/java/org/tarantool/jdbc/SQLConnection.java
@@ -1,8 +1,15 @@
package org.tarantool.jdbc;
+import static org.tarantool.jdbc.SQLDriver.PROP_HOST;
+import static org.tarantool.jdbc.SQLDriver.PROP_PASSWORD;
+import static org.tarantool.jdbc.SQLDriver.PROP_PORT;
+import static org.tarantool.jdbc.SQLDriver.PROP_SOCKET_TIMEOUT;
+import static org.tarantool.jdbc.SQLDriver.PROP_USER;
+
import org.tarantool.CommunicationException;
import org.tarantool.JDBCBridge;
import org.tarantool.TarantoolConnection;
+import org.tarantool.util.JdbcConstants;
import org.tarantool.util.SQLStates;
import java.io.IOException;
@@ -12,6 +19,7 @@
import java.sql.Array;
import java.sql.Blob;
import java.sql.CallableStatement;
+import java.sql.ClientInfoStatus;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
@@ -29,21 +37,23 @@
import java.sql.Statement;
import java.sql.Struct;
import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Executor;
-import static org.tarantool.jdbc.SQLDriver.PROP_HOST;
-import static org.tarantool.jdbc.SQLDriver.PROP_PASSWORD;
-import static org.tarantool.jdbc.SQLDriver.PROP_PORT;
-import static org.tarantool.jdbc.SQLDriver.PROP_SOCKET_TIMEOUT;
-import static org.tarantool.jdbc.SQLDriver.PROP_USER;
-
-@SuppressWarnings("Since15")
+/**
+ * Tarantool {@link Connection} implementation.
+ *
+ * Supports creating {@link Statement} and {@link PreparedStatement} instances
+ */
public class SQLConnection implements Connection {
private static final int UNSET_HOLDABILITY = 0;
+ private static final String PING_QUERY = "SELECT 1";
private final TarantoolConnection connection;
@@ -72,8 +82,9 @@ public class SQLConnection implements Connection {
// No-op.
}
}
- if (e instanceof SQLException)
+ if (e instanceof SQLException) {
throw (SQLException) e;
+ }
throw new SQLException("Couldn't initiate connection using " + SQLDriver.diagProperties(properties), e);
}
}
@@ -91,6 +102,7 @@ public class SQLConnection implements Connection {
* to honor this timeout for the following read/write operations as well.
*
* @return Connected socket.
+ *
* @throws SQLException if failed.
*/
protected Socket getConnectedSocket() throws SQLException {
@@ -140,13 +152,17 @@ protected Socket makeSocket() {
* @param user User name.
* @param pass Password.
* @param socket Connected socket.
+ *
* @return Native tarantool connection.
+ *
* @throws IOException if failed.
*/
protected TarantoolConnection makeConnection(String user, String pass, Socket socket) throws IOException {
- return new TarantoolConnection(user, pass, socket) {{
- msgPackLite = SQLMsgPackLite.INSTANCE;
- }};
+ return new TarantoolConnection(user, pass, socket) {
+ {
+ msgPackLite = SQLMsgPackLite.INSTANCE;
+ }
+ };
}
@Override
@@ -154,42 +170,137 @@ public Statement createStatement() throws SQLException {
return createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
}
+ @Override
+ public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {
+ return createStatement(resultSetType, resultSetConcurrency, getHoldability());
+ }
+
+ @Override
+ public Statement createStatement(int resultSetType,
+ int resultSetConcurrency,
+ int resultSetHoldability) throws SQLException {
+ checkNotClosed();
+ checkStatementParams(resultSetType, resultSetConcurrency, resultSetHoldability);
+ return new SQLStatement(this, resultSetType, resultSetConcurrency, resultSetHoldability);
+ }
+
@Override
public PreparedStatement prepareStatement(String sql) throws SQLException {
return prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
}
+ @Override
+ public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency)
+ throws SQLException {
+ return prepareStatement(sql, resultSetType, resultSetConcurrency, getHoldability());
+ }
+
+ @Override
+ public PreparedStatement prepareStatement(String sql,
+ int resultSetType,
+ int resultSetConcurrency,
+ int resultSetHoldability) throws SQLException {
+ checkNotClosed();
+ checkStatementParams(resultSetType, resultSetConcurrency, resultSetHoldability);
+ return new SQLPreparedStatement(this, sql, resultSetType, resultSetConcurrency, resultSetHoldability);
+ }
+
+ @Override
+ public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {
+ checkNotClosed();
+ JdbcConstants.checkGeneratedKeysConstant(autoGeneratedKeys);
+ if (autoGeneratedKeys != Statement.NO_GENERATED_KEYS) {
+ throw new SQLFeatureNotSupportedException();
+ }
+ return prepareStatement(sql);
+ }
+
+ @Override
+ public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException {
+ checkNotClosed();
+ throw new SQLFeatureNotSupportedException();
+ }
+
+ @Override
+ public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException {
+ checkNotClosed();
+ throw new SQLFeatureNotSupportedException();
+ }
+
@Override
public CallableStatement prepareCall(String sql) throws SQLException {
+ return prepareCall(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
+ }
+
+ @Override
+ public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
+ return prepareCall(sql, resultSetType, resultSetConcurrency, getHoldability());
+ }
+
+ @Override
+ public CallableStatement prepareCall(String sql,
+ int resultSetType,
+ int resultSetConcurrency,
+ int resultSetHoldability)
+ throws SQLException {
+ checkNotClosed();
throw new SQLFeatureNotSupportedException();
}
@Override
public String nativeSQL(String sql) throws SQLException {
+ checkNotClosed();
throw new SQLFeatureNotSupportedException();
}
@Override
public void setAutoCommit(boolean autoCommit) throws SQLException {
- if (autoCommit == false) {
+ checkNotClosed();
+ if (!autoCommit) {
throw new SQLFeatureNotSupportedException();
}
}
@Override
public boolean getAutoCommit() throws SQLException {
+ checkNotClosed();
return true;
}
@Override
public void commit() throws SQLException {
+ checkNotClosed();
+ if (getAutoCommit()) {
+ throw new SQLNonTransientException(
+ "Cannot commit when auto-commit is enabled.",
+ SQLStates.INVALID_TRANSACTION_STATE.getSqlState()
+ );
+ }
throw new SQLFeatureNotSupportedException();
}
@Override
public void rollback() throws SQLException {
+ checkNotClosed();
+ if (getAutoCommit()) {
+ throw new SQLNonTransientException(
+ "Cannot rollback when auto-commit is enabled.",
+ SQLStates.INVALID_TRANSACTION_STATE.getSqlState()
+ );
+ }
throw new SQLFeatureNotSupportedException();
+ }
+ @Override
+ public void rollback(Savepoint savepoint) throws SQLException {
+ checkNotClosed();
+ if (getAutoCommit()) {
+ throw new SQLNonTransientException(
+ "Cannot roll back to a savepoint when auto-commit is enabled.",
+ SQLStates.INVALID_TRANSACTION_STATE.getSqlState()
+ );
+ }
+ throw new SQLFeatureNotSupportedException();
}
@Override
@@ -213,25 +324,30 @@ public DatabaseMetaData getMetaData() throws SQLException {
@Override
public void setReadOnly(boolean readOnly) throws SQLException {
-
+ checkNotClosed();
+ throw new SQLFeatureNotSupportedException();
}
@Override
public boolean isReadOnly() throws SQLException {
+ checkNotClosed();
return false;
}
@Override
public void setCatalog(String catalog) throws SQLException {
+ checkNotClosed();
}
@Override
public String getCatalog() throws SQLException {
+ checkNotClosed();
return null;
}
@Override
public void setTransactionIsolation(int level) throws SQLException {
+ checkNotClosed();
if (level != Connection.TRANSACTION_NONE) {
throw new SQLFeatureNotSupportedException();
}
@@ -239,42 +355,30 @@ public void setTransactionIsolation(int level) throws SQLException {
@Override
public int getTransactionIsolation() throws SQLException {
+ checkNotClosed();
return Connection.TRANSACTION_NONE;
}
@Override
public SQLWarning getWarnings() throws SQLException {
+ checkNotClosed();
return null;
}
@Override
public void clearWarnings() throws SQLException {
-
- }
-
- @Override
- public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {
- return createStatement(resultSetType, resultSetConcurrency, getHoldability());
- }
-
- @Override
- public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency)
- throws SQLException {
- return prepareStatement(sql, resultSetType, resultSetConcurrency, getHoldability());
- }
-
- @Override
- public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
- throw new SQLFeatureNotSupportedException();
+ checkNotClosed();
}
@Override
public Map> getTypeMap() throws SQLException {
+ checkNotClosed();
throw new SQLFeatureNotSupportedException();
}
@Override
public void setTypeMap(Map> map) throws SQLException {
+ checkNotClosed();
throw new SQLFeatureNotSupportedException();
}
@@ -296,130 +400,189 @@ public int getHoldability() throws SQLException {
@Override
public Savepoint setSavepoint() throws SQLException {
+ checkNotClosed();
+ if (getAutoCommit()) {
+ throw new SQLNonTransientException(
+ "Cannot set a savepoint when auto-commit is enabled.",
+ SQLStates.INVALID_TRANSACTION_STATE.getSqlState()
+ );
+ }
throw new SQLFeatureNotSupportedException();
}
@Override
public Savepoint setSavepoint(String name) throws SQLException {
- throw new SQLFeatureNotSupportedException();
- }
-
- @Override
- public void rollback(Savepoint savepoint) throws SQLException {
+ checkNotClosed();
+ if (getAutoCommit()) {
+ throw new SQLNonTransientException(
+ "Cannot set a savepoint when auto-commit is enabled.",
+ SQLStates.INVALID_TRANSACTION_STATE.getSqlState()
+ );
+ }
throw new SQLFeatureNotSupportedException();
}
@Override
public void releaseSavepoint(Savepoint savepoint) throws SQLException {
+ checkNotClosed();
throw new SQLFeatureNotSupportedException();
}
@Override
- public Statement createStatement(int resultSetType,
- int resultSetConcurrency,
- int resultSetHoldability) throws SQLException {
+ public Clob createClob() throws SQLException {
checkNotClosed();
- checkHoldabilitySupport(resultSetHoldability);
- return new SQLStatement(this, resultSetType, resultSetConcurrency, resultSetHoldability);
+ throw new SQLFeatureNotSupportedException();
}
@Override
- public PreparedStatement prepareStatement(String sql,
- int resultSetType,
- int resultSetConcurrency,
- int resultSetHoldability) throws SQLException {
+ public Blob createBlob() throws SQLException {
checkNotClosed();
- checkHoldabilitySupport(resultSetHoldability);
- return new SQLPreparedStatement(this, sql, resultSetType, resultSetConcurrency, resultSetHoldability);
- }
-
- @Override
- public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability)
- throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {
+ public NClob createNClob() throws SQLException {
+ checkNotClosed();
throw new SQLFeatureNotSupportedException();
}
@Override
- public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException {
+ public SQLXML createSQLXML() throws SQLException {
+ checkNotClosed();
throw new SQLFeatureNotSupportedException();
}
+ /**
+ * {@inheritDoc}
+ *
+ * @param timeout temporally ignored param
+ *
+ * @return connection activity status
+ */
@Override
- public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException {
- throw new SQLFeatureNotSupportedException();
+ public boolean isValid(int timeout) throws SQLException {
+ if (timeout < 0) {
+ throw new SQLNonTransientException(
+ "Timeout cannot be negative",
+ SQLStates.INVALID_PARAMETER_VALUE.getSqlState()
+ );
+ }
+ if (isClosed()) {
+ return false;
+ }
+ return checkConnection(timeout);
}
- @Override
- public Clob createClob() throws SQLException {
- throw new SQLFeatureNotSupportedException();
- }
+ private boolean checkConnection(int timeout) {
+ ResultSet resultSet = null;
+ try (Statement pingStatement = createStatement()) {
+ // todo: before use timeout we need to provide query timeout per statement
- @Override
- public Blob createBlob() throws SQLException {
- throw new SQLFeatureNotSupportedException();
- }
+ resultSet = pingStatement.executeQuery(PING_QUERY);
+ boolean isValid = resultSet.next() && resultSet.getInt(1) == 1;
+ resultSet.close();
- @Override
- public NClob createNClob() throws SQLException {
- throw new SQLFeatureNotSupportedException();
+ return isValid;
+ } catch (SQLException e) {
+ return false;
+ }
}
@Override
- public SQLXML createSQLXML() throws SQLException {
- throw new SQLFeatureNotSupportedException();
+ public void setClientInfo(String name, String value) throws SQLClientInfoException {
+ try {
+ checkNotClosed();
+ } catch (SQLException cause) {
+ throwUnknownReasonClientProperties("Connection is closed", Collections.singleton(name), cause);
+ }
+ throwUnknownClientProperties(Collections.singleton(name));
}
@Override
- public boolean isValid(int timeout) throws SQLException {
- return true;
+ public void setClientInfo(Properties properties) throws SQLClientInfoException {
+ try {
+ checkNotClosed();
+ } catch (SQLException cause) {
+ throwUnknownReasonClientProperties("Connection is closed", properties.keySet(), cause);
+ }
+ throwUnknownClientProperties(properties.keySet());
}
- @Override
- public void setClientInfo(String name, String value) throws SQLClientInfoException {
- throw new SQLClientInfoException();
+ /**
+ * Throws an exception caused by {@code cause} and marks all properties
+ * as {@link ClientInfoStatus#REASON_UNKNOWN}.
+ *
+ * @param reason reason mesage
+ * @param properties client properties
+ * @param cause original cause
+ *
+ * @throws SQLClientInfoException wrapped exception
+ */
+ private void throwUnknownReasonClientProperties(String reason,
+ Collection properties,
+ SQLException cause) throws SQLClientInfoException {
+ Map failedProperties = new HashMap<>();
+ properties.forEach(property -> {
+ failedProperties.put(property.toString(), ClientInfoStatus.REASON_UNKNOWN);
+ });
+ throw new SQLClientInfoException(reason, cause.getSQLState(), failedProperties, cause);
}
- @Override
- public void setClientInfo(Properties properties) throws SQLClientInfoException {
- throw new SQLClientInfoException();
+ /**
+ * Throws exception for unrecognizable properties.
+ *
+ * @param properties unknown property names.
+ *
+ * @throws SQLClientInfoException wrapped exception
+ */
+ private void throwUnknownClientProperties(Collection properties) throws SQLClientInfoException {
+ Map failedProperties = new HashMap<>();
+ properties.forEach(property -> {
+ failedProperties.put(property.toString(), ClientInfoStatus.REASON_UNKNOWN_PROPERTY);
+ });
+ throw new SQLClientInfoException(failedProperties);
}
@Override
public String getClientInfo(String name) throws SQLException {
+ checkNotClosed();
throw new SQLFeatureNotSupportedException();
}
@Override
public Properties getClientInfo() throws SQLException {
+ checkNotClosed();
throw new SQLFeatureNotSupportedException();
}
@Override
public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
+ checkNotClosed();
throw new SQLFeatureNotSupportedException();
}
@Override
public Struct createStruct(String typeName, Object[] attributes) throws SQLException {
+ checkNotClosed();
throw new SQLFeatureNotSupportedException();
}
@Override
public void setSchema(String schema) throws SQLException {
+ checkNotClosed();
}
@Override
public String getSchema() throws SQLException {
+ checkNotClosed();
return null;
}
@Override
public void abort(Executor executor) throws SQLException {
+ if (isClosed()) {
+ return;
+ }
throw new SQLFeatureNotSupportedException();
}
@@ -427,8 +590,9 @@ public void abort(Executor executor) throws SQLException {
public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException {
checkNotClosed();
- if (milliseconds < 0)
+ if (milliseconds < 0) {
throw new SQLException("Network timeout cannot be negative.");
+ }
try {
connection.setSocketTimeout(milliseconds);
@@ -491,7 +655,7 @@ protected int executeUpdate(String sql, Object... args) throws SQLException {
}
protected List> nativeSelect(Integer space, Integer index, List> key, int offset, int limit, int iterator)
- throws SQLException {
+ throws SQLException {
checkNotClosed();
try {
return connection.select(space, index, key, offset, limit, iterator);
@@ -506,14 +670,17 @@ protected String getServerVersion() {
}
/**
+ * Checks connection close status.
+ *
* @throws SQLException If connection is closed.
*/
protected void checkNotClosed() throws SQLException {
- if (isClosed())
+ if (isClosed()) {
throw new SQLNonTransientConnectionException(
- "Connection is closed.",
- SQLStates.CONNECTION_DOES_NOT_EXIST.getSqlState()
+ "Connection is closed.",
+ SQLStates.CONNECTION_DOES_NOT_EXIST.getSqlState()
);
+ }
}
String getUrl() {
@@ -531,7 +698,7 @@ Properties getProperties() {
*/
private void handleException(Exception e) {
if (CommunicationException.class.isAssignableFrom(e.getClass()) ||
- IOException.class.isAssignableFrom(e.getClass())) {
+ IOException.class.isAssignableFrom(e.getClass())) {
try {
close();
} catch (SQLException ignored) {
@@ -541,17 +708,70 @@ private void handleException(Exception e) {
}
/**
- * Checks whether holdability
is supported
+ * Checks all params required to make statements.
+ *
+ * @param resultSetType scroll type
+ * @param resultSetConcurrency concurrency level
+ * @param resultSetHoldability holdability type
+ *
+ * @throws SQLFeatureNotSupportedException if any param is not supported
+ * @throws SQLNonTransientException if any param has an invalid value
+ */
+ private void checkStatementParams(int resultSetType,
+ int resultSetConcurrency,
+ int resultSetHoldability) throws SQLException {
+ checkResultSetType(resultSetType);
+ checkResultSetConcurrency(resultSetType, resultSetConcurrency);
+ checkHoldabilitySupport(resultSetHoldability);
+ }
+
+ /**
+ * Checks whether resultSetType
is supported.
+ *
+ * @param resultSetType param to be checked
*
- * @param holdability param to be checked
* @throws SQLFeatureNotSupportedException param is not supported
- * @throws SQLNonTransientException param has invalid value
+ * @throws SQLNonTransientException param has invalid value
*/
- private void checkHoldabilitySupport(int holdability) throws SQLException {
- if (holdability != ResultSet.CLOSE_CURSORS_AT_COMMIT
- && holdability != ResultSet.HOLD_CURSORS_OVER_COMMIT) {
+ private void checkResultSetType(int resultSetType) throws SQLException {
+ if (resultSetType != ResultSet.TYPE_FORWARD_ONLY &&
+ resultSetType != ResultSet.TYPE_SCROLL_INSENSITIVE &&
+ resultSetType != ResultSet.TYPE_SCROLL_SENSITIVE) {
throw new SQLNonTransientException("", SQLStates.INVALID_PARAMETER_VALUE.getSqlState());
}
+ if (!getMetaData().supportsResultSetType(resultSetType)) {
+ throw new SQLFeatureNotSupportedException();
+ }
+ }
+
+ /**
+ * Checks whether resultSetType
is supported.
+ *
+ * @param resultSetConcurrency param to be checked
+ *
+ * @throws SQLFeatureNotSupportedException param is not supported
+ * @throws SQLNonTransientException param has invalid value
+ */
+ private void checkResultSetConcurrency(int resultSetType, int resultSetConcurrency) throws SQLException {
+ if (resultSetConcurrency != ResultSet.CONCUR_READ_ONLY &&
+ resultSetConcurrency != ResultSet.CONCUR_UPDATABLE) {
+ throw new SQLNonTransientException("", SQLStates.INVALID_PARAMETER_VALUE.getSqlState());
+ }
+ if (!getMetaData().supportsResultSetConcurrency(resultSetType, resultSetConcurrency)) {
+ throw new SQLFeatureNotSupportedException();
+ }
+ }
+
+ /**
+ * Checks whether holdability
is supported.
+ *
+ * @param holdability param to be checked
+ *
+ * @throws SQLFeatureNotSupportedException param is not supported
+ * @throws SQLNonTransientException param has invalid value
+ */
+ private void checkHoldabilitySupport(int holdability) throws SQLException {
+ JdbcConstants.checkHoldabilityConstant(holdability);
if (!getMetaData().supportsResultSetHoldability(holdability)) {
throw new SQLFeatureNotSupportedException();
}
@@ -562,9 +782,11 @@ private void checkHoldabilitySupport(int holdability) throws SQLException {
*
* @param sql SQL Text.
* @param params Parameters of the SQL statement.
+ *
* @return Formatted error message.
*/
private static String formatError(String sql, Object... params) {
return "Failed to execute SQL: " + sql + ", params: " + Arrays.deepToString(params);
}
+
}
diff --git a/src/main/java/org/tarantool/jdbc/SQLDatabaseMetadata.java b/src/main/java/org/tarantool/jdbc/SQLDatabaseMetadata.java
index d8f3c2bf..1605fd86 100644
--- a/src/main/java/org/tarantool/jdbc/SQLDatabaseMetadata.java
+++ b/src/main/java/org/tarantool/jdbc/SQLDatabaseMetadata.java
@@ -8,7 +8,7 @@
import java.sql.ResultSet;
import java.sql.RowIdLifetime;
import java.sql.SQLException;
-import java.sql.SQLFeatureNotSupportedException;
+import java.sql.SQLNonTransientException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Arrays;
@@ -36,17 +36,17 @@ public SQLNullResultSet(JDBCBridge bridge, SQLStatement ownerStatement) throws S
}
@Override
- protected Object getRaw(int columnIndex) {
- return columnIndex > getCurrentRow().size() ? null : getCurrentRow().get(columnIndex - 1);
+ protected Object getRaw(int columnIndex) throws SQLException {
+ List row = getCurrentRow();
+ return columnIndex > row.size() ? null : row.get(columnIndex - 1);
}
@Override
- protected Integer getColumnIndex(String columnLabel) {
- Integer idx = super.getColumnIndex(columnLabel);
- return idx == null ? Integer.MAX_VALUE : idx;
+ protected int findColumnIndex(String columnLabel) throws SQLException {
+ int index = super.findColumnIndex(columnLabel);
+ return index == 0 ? Integer.MAX_VALUE : index;
}
-
}
public SQLDatabaseMetadata(SQLConnection connection) {
@@ -80,22 +80,22 @@ public boolean isReadOnly() throws SQLException {
@Override
public boolean nullsAreSortedHigh() throws SQLException {
- return true;
+ return false;
}
@Override
public boolean nullsAreSortedLow() throws SQLException {
- return !nullsAreSortedHigh();
+ return true;
}
@Override
public boolean nullsAreSortedAtStart() throws SQLException {
- return true;
+ return false;
}
@Override
public boolean nullsAreSortedAtEnd() throws SQLException {
- return !nullsAreSortedAtStart();
+ return false;
}
@Override
@@ -645,13 +645,16 @@ public boolean dataDefinitionIgnoredInTransactions() throws SQLException {
@Override
public ResultSet getProcedures(String catalog, String schemaPattern, String procedureNamePattern)
- throws SQLException {
+ throws SQLException {
return asMetadataResultSet(JDBCBridge.EMPTY);
}
@Override
- public ResultSet getProcedureColumns(String catalog, String schemaPattern, String procedureNamePattern, String columnNamePattern)
- throws SQLException {
+ public ResultSet getProcedureColumns(String catalog,
+ String schemaPattern,
+ String procedureNamePattern,
+ String columnNamePattern)
+ throws SQLException {
return asMetadataResultSet(JDBCBridge.EMPTY);
}
@@ -678,8 +681,10 @@ public ResultSet getTables(String catalog, String schemaPattern, String tableNam
connection.checkNotClosed();
return asMetadataResultSet(JDBCBridge.EMPTY);
}
- String[] parts = tableNamePattern == null ? new String[]{""} : tableNamePattern.split("%");
- List> spaces = (List>) connection.nativeSelect(_VSPACE, 0, Arrays.asList(), 0, SPACES_MAX, 0);
+ String[] parts = tableNamePattern == null ? new String[] { "" } : tableNamePattern.split("%");
+ List> spaces = (List>) connection.nativeSelect(
+ _VSPACE, 0, Arrays.asList(), 0, SPACES_MAX, 0
+ );
List> rows = new ArrayList>();
for (List space : spaces) {
String tableName = (String) space.get(NAME_IDX);
@@ -693,18 +698,19 @@ public ResultSet getTables(String catalog, String schemaPattern, String tableNam
}
}
List columnNames = Arrays.asList(
- "TABLE_NAME", "TABLE_TYPE",
- //nulls
- "REMARKS", "TABLE_CAT",
- "TABLE_SCHEM", "TABLE_TYPE",
- "TYPE_CAT", "TYPE_SCHEM",
- "TYPE_NAME", "SELF_REFERENCING_COL_NAME",
- "REF_GENERATION"
+ "TABLE_NAME", "TABLE_TYPE",
+ //nulls
+ "REMARKS", "TABLE_CAT",
+ "TABLE_SCHEM", "TABLE_TYPE",
+ "TYPE_CAT", "TYPE_SCHEM",
+ "TYPE_NAME", "SELF_REFERENCING_COL_NAME",
+ "REF_GENERATION"
);
return sqlNullResultSet(columnNames, rows);
} catch (Exception e) {
- throw new SQLException("Failed to retrieve table(s) description: " +
- "tableNamePattern=\"" + tableNamePattern + "\".", e);
+ throw new SQLException(
+ "Failed to retrieve table(s) description: " +
+ "tableNamePattern=\"" + tableNamePattern + "\".", e);
}
}
@@ -713,6 +719,11 @@ public ResultSet getSchemas() throws SQLException {
return rowOfNullsResultSet();
}
+ @Override
+ public ResultSet getSchemas(String catalog, String schemaPattern) throws SQLException {
+ return rowOfNullsResultSet();
+ }
+
@Override
public ResultSet getCatalogs() throws SQLException {
return rowOfNullsResultSet();
@@ -720,17 +731,19 @@ public ResultSet getCatalogs() throws SQLException {
@Override
public ResultSet getTableTypes() throws SQLException {
- return asMetadataResultSet(JDBCBridge.mock(Arrays.asList("TABLE_TYPE"), Arrays.asList(Arrays.asList("TABLE"))));
+ return asMetadataResultSet(JDBCBridge.mock(Arrays.asList("TABLE_TYPE"), Arrays.asList(Arrays.asList("TABLE"))));
}
@Override
public ResultSet getColumns(String catalog, String schemaPattern, String tableNamePattern, String columnNamePattern)
- throws SQLException {
+ throws SQLException {
try {
- String[] tableParts = tableNamePattern == null ? new String[]{""} : tableNamePattern.split("%");
- String[] colParts = columnNamePattern == null ? new String[]{""} : columnNamePattern.split("%");
- List> spaces = (List>) connection.nativeSelect(_VSPACE, 0, Arrays.asList(), 0, SPACES_MAX, 0);
- List> rows = new ArrayList>();
+ String[] tableParts = tableNamePattern == null ? new String[] { "" } : tableNamePattern.split("%");
+ String[] colParts = columnNamePattern == null ? new String[] { "" } : columnNamePattern.split("%");
+ List> spaces = (List>) connection.nativeSelect(
+ _VSPACE, 0, Arrays.asList(), 0, SPACES_MAX, 0
+ );
+ List> rows = new ArrayList<>();
for (List space : spaces) {
String tableName = (String) space.get(NAME_IDX);
List> format = (List>) space.get(FORMAT_IDX);
@@ -744,53 +757,60 @@ public ResultSet getColumns(String catalog, String schemaPattern, String tableNa
String columnName = (String) f.get("name");
String dbType = (String) f.get("type");
if (like(columnName, colParts)) {
- rows.add(Arrays.asList(tableName, columnName, columnIdx, Types.OTHER, dbType, 10, 1, "YES", Types.OTHER, "NO", "NO"));
+ rows.add(Arrays.asList(
+ tableName, columnName,
+ columnIdx, Types.OTHER,
+ dbType, 10, 1,
+ "YES", Types.OTHER,
+ "NO", "NO")
+ );
}
}
}
}
List columnNames = Arrays.asList(
- "TABLE_NAME", "COLUMN_NAME",
- "ORDINAL_POSITION", "DATA_TYPE",
- "TYPE_NAME", "NUM_PREC_RADIX",
- "NULLABLE", "IS_NULLABLE",
- "SOURCE_DATA_TYPE", "IS_AUTOINCREMENT",
- "IS_GENERATEDCOLUMN",
- //nulls
- "TABLE_CAT", "TABLE_SCHEM",
- "COLUMN_SIZE", "BUFFER_LENGTH",
- "DECIMAL_DIGITS", "REMARKS",
- "COLUMN_DEF", "SQL_DATA_TYPE",
- "SQL_DATETIME_SUB", "CHAR_OCTET_LENGTH",
- "SCOPE_CATALOG", "SCOPE_SCHEMA",
- "SCOPE_TABLE"
+ "TABLE_NAME", "COLUMN_NAME",
+ "ORDINAL_POSITION", "DATA_TYPE",
+ "TYPE_NAME", "NUM_PREC_RADIX",
+ "NULLABLE", "IS_NULLABLE",
+ "SOURCE_DATA_TYPE", "IS_AUTOINCREMENT",
+ "IS_GENERATEDCOLUMN",
+ //nulls
+ "TABLE_CAT", "TABLE_SCHEM",
+ "COLUMN_SIZE", "BUFFER_LENGTH",
+ "DECIMAL_DIGITS", "REMARKS",
+ "COLUMN_DEF", "SQL_DATA_TYPE",
+ "SQL_DATETIME_SUB", "CHAR_OCTET_LENGTH",
+ "SCOPE_CATALOG", "SCOPE_SCHEMA",
+ "SCOPE_TABLE"
);
return sqlNullResultSet(
- columnNames,
- rows);
+ columnNames,
+ rows);
} catch (Exception e) {
- throw new SQLException("Error processing table column metadata: " +
- "tableNamePattern=\"" + tableNamePattern + "\"; " +
- "columnNamePattern=\"" + columnNamePattern + "\".", e);
+ throw new SQLException(
+ "Error processing table column metadata: " +
+ "tableNamePattern=\"" + tableNamePattern + "\"; " +
+ "columnNamePattern=\"" + columnNamePattern + "\".", e);
}
}
@Override
public ResultSet getColumnPrivileges(String catalog, String schema, String table, String columnNamePattern)
- throws SQLException {
+ throws SQLException {
return rowOfNullsResultSet();
}
@Override
public ResultSet getTablePrivileges(String catalog, String schemaPattern, String tableNamePattern)
- throws SQLException {
+ throws SQLException {
return rowOfNullsResultSet();
}
@Override
public ResultSet getBestRowIdentifier(String catalog, String schema, String table, int scope, boolean nullable)
- throws SQLException {
+ throws SQLException {
return asMetadataResultSet(JDBCBridge.EMPTY);
}
@@ -802,7 +822,8 @@ public ResultSet getVersionColumns(String catalog, String schema, String table)
@Override
public ResultSet getPrimaryKeys(String catalog, String schema, String table) throws SQLException {
final List colNames = Arrays.asList(
- "TABLE_CAT", "TABLE_SCHEM", "TABLE_NAME", "COLUMN_NAME", "KEY_SEQ", "PK_NAME");
+ "TABLE_CAT", "TABLE_SCHEM", "TABLE_NAME", "COLUMN_NAME", "KEY_SEQ", "PK_NAME"
+ );
if (table == null || table.isEmpty()) {
connection.checkNotClosed();
@@ -812,8 +833,9 @@ public ResultSet getPrimaryKeys(String catalog, String schema, String table) thr
try {
List spaces = connection.nativeSelect(_VSPACE, 2, Collections.singletonList(table), 0, 1, 0);
- if (spaces == null || spaces.size() == 0)
+ if (spaces == null || spaces.size() == 0) {
return emptyResultSet(colNames);
+ }
List space = ensureType(List.class, spaces.get(0));
List fields = ensureType(List.class, space.get(FORMAT_IDX));
@@ -822,13 +844,14 @@ public ResultSet getPrimaryKeys(String catalog, String schema, String table) thr
List primaryKey = ensureType(List.class, indexes.get(0));
List parts = ensureType(List.class, primaryKey.get(INDEX_FORMAT_IDX));
- List> rows = new ArrayList>();
+ List> rows = new ArrayList<>();
for (int i = 0; i < parts.size(); i++) {
// For native spaces, the 'parts' is 'List of Lists'.
// We only accept SQL spaces, for which the parts is 'List of Maps'.
Map part = checkType(Map.class, parts.get(i));
- if (part == null)
+ if (part == null) {
return emptyResultSet(colNames);
+ }
int ordinal = ensureType(Number.class, part.get("field")).intValue();
Map field = ensureType(Map.class, fields.get(ordinal));
@@ -862,8 +885,13 @@ public ResultSet getExportedKeys(String catalog, String schema, String table) th
}
@Override
- public ResultSet getCrossReference(String parentCatalog, String parentSchema, String parentTable, String foreignCatalog, String foreignSchema, String foreignTable)
- throws SQLException {
+ public ResultSet getCrossReference(String parentCatalog,
+ String parentSchema,
+ String parentTable,
+ String foreignCatalog,
+ String foreignSchema,
+ String foreignTable)
+ throws SQLException {
return asMetadataResultSet(JDBCBridge.EMPTY);
}
@@ -874,18 +902,19 @@ public ResultSet getTypeInfo() throws SQLException {
@Override
public ResultSet getIndexInfo(String catalog, String schema, String table, boolean unique, boolean approximate)
- throws SQLException {
+ throws SQLException {
return asMetadataResultSet(JDBCBridge.EMPTY);
}
@Override
public boolean supportsResultSetType(int type) throws SQLException {
- return false;
+ return type == ResultSet.TYPE_FORWARD_ONLY ||
+ type == ResultSet.TYPE_SCROLL_INSENSITIVE;
}
@Override
public boolean supportsResultSetConcurrency(int type, int concurrency) throws SQLException {
- return false;
+ return supportsResultSetType(type) && concurrency == ResultSet.CONCUR_READ_ONLY;
}
@Override
@@ -940,7 +969,7 @@ public boolean supportsBatchUpdates() throws SQLException {
@Override
public ResultSet getUDTs(String catalog, String schemaPattern, String typeNamePattern, int[] types)
- throws SQLException {
+ throws SQLException {
return asMetadataResultSet(JDBCBridge.EMPTY);
}
@@ -980,14 +1009,17 @@ public ResultSet getSuperTables(String catalog, String schemaPattern, String tab
}
@Override
- public ResultSet getAttributes(String catalog, String schemaPattern, String typeNamePattern, String attributeNamePattern)
- throws SQLException {
+ public ResultSet getAttributes(String catalog,
+ String schemaPattern,
+ String typeNamePattern,
+ String attributeNamePattern)
+ throws SQLException {
return asMetadataResultSet(JDBCBridge.EMPTY);
}
/**
* {@inheritDoc}
- *
+ *
* Support of {@link ResultSet#CLOSE_CURSORS_AT_COMMIT} is not
* available now because it requires cursor transaction support.
*/
@@ -1041,11 +1073,6 @@ public RowIdLifetime getRowIdLifetime() throws SQLException {
return RowIdLifetime.ROWID_UNSUPPORTED;
}
- @Override
- public ResultSet getSchemas(String catalog, String schemaPattern) throws SQLException {
- return rowOfNullsResultSet();
- }
-
@Override
public boolean supportsStoredFunctionsUsingCallSyntax() throws SQLException {
return false;
@@ -1063,19 +1090,25 @@ public ResultSet getClientInfoProperties() throws SQLException {
@Override
public ResultSet getFunctions(String catalog, String schemaPattern, String functionNamePattern)
- throws SQLException {
+ throws SQLException {
return asMetadataResultSet(JDBCBridge.EMPTY);
}
@Override
- public ResultSet getFunctionColumns(String catalog, String schemaPattern, String functionNamePattern, String columnNamePattern)
- throws SQLException {
+ public ResultSet getFunctionColumns(String catalog,
+ String schemaPattern,
+ String functionNamePattern,
+ String columnNamePattern)
+ throws SQLException {
return asMetadataResultSet(JDBCBridge.EMPTY);
}
@Override
- public ResultSet getPseudoColumns(String catalog, String schemaPattern, String tableNamePattern, String columnNamePattern)
- throws SQLException {
+ public ResultSet getPseudoColumns(String catalog,
+ String schemaPattern,
+ String tableNamePattern,
+ String columnNamePattern)
+ throws SQLException {
return asMetadataResultSet(JDBCBridge.EMPTY);
}
@@ -1085,13 +1118,16 @@ public boolean generatedKeyAlwaysReturned() throws SQLException {
}
@Override
- public T unwrap(Class iface) throws SQLException {
- throw new SQLFeatureNotSupportedException();
+ public T unwrap(Class type) throws SQLException {
+ if (isWrapperFor(type)) {
+ return type.cast(this);
+ }
+ throw new SQLNonTransientException("SQLDatabaseMetadata does not wrap " + type.getName());
}
@Override
- public boolean isWrapperFor(Class> iface) throws SQLException {
- throw new SQLFeatureNotSupportedException();
+ public boolean isWrapperFor(Class> type) throws SQLException {
+ return type.isAssignableFrom(this.getClass());
}
private ResultSet asMetadataResultSet(JDBCBridge jdbcBridge) throws SQLException {
@@ -1105,7 +1141,7 @@ private SQLStatement createMetadataStatement() throws SQLException {
private static T ensureType(Class cls, Object v) throws Exception {
if (v == null || !cls.isAssignableFrom(v.getClass())) {
throw new Exception(String.format("Wrong value type '%s', expected '%s'.",
- v == null ? "null" : v.getClass().getName(), cls.getName()));
+ v == null ? "null" : v.getClass().getName(), cls.getName()));
}
return cls.cast(v);
}
diff --git a/src/main/java/org/tarantool/jdbc/SQLDriver.java b/src/main/java/org/tarantool/jdbc/SQLDriver.java
index 6ca97866..c73ae96e 100644
--- a/src/main/java/org/tarantool/jdbc/SQLDriver.java
+++ b/src/main/java/org/tarantool/jdbc/SQLDriver.java
@@ -12,13 +12,12 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;
-@SuppressWarnings("Since15")
public class SQLDriver implements Driver {
static {
try {
java.sql.DriverManager.registerDriver(new SQLDriver());
- } catch (SQLException E) {
+ } catch (SQLException e) {
throw new RuntimeException("Can't register driver!");
}
}
@@ -31,11 +30,13 @@ public class SQLDriver implements Driver {
public static final String PROP_SOCKET_TIMEOUT = "socketTimeout";
// Define default values once here.
- final static Properties defaults = new Properties() {{
- setProperty(PROP_HOST, "localhost");
- setProperty(PROP_PORT, "3301");
- setProperty(PROP_SOCKET_TIMEOUT, "0");
- }};
+ static final Properties defaults = new Properties() {
+ {
+ setProperty(PROP_HOST, "localhost");
+ setProperty(PROP_PORT, "3301");
+ setProperty(PROP_SOCKET_TIMEOUT, "0");
+ }
+ };
private final Map providerCache = new ConcurrentHashMap();
@@ -45,8 +46,9 @@ public Connection connect(String url, Properties info) throws SQLException {
final Properties urlProperties = parseQueryString(uri, info);
String providerClassName = urlProperties.getProperty(PROP_SOCKET_PROVIDER);
- if (providerClassName == null)
+ if (providerClassName == null) {
return new SQLConnection(url, urlProperties);
+ }
final SQLSocketProvider provider = getSocketProviderInstance(providerClassName);
@@ -54,8 +56,9 @@ public Connection connect(String url, Properties info) throws SQLException {
@Override
protected Socket getConnectedSocket() throws SQLException {
Socket socket = provider.getConnectedSocket(uri, urlProperties);
- if (socket == null)
+ if (socket == null) {
throw new SQLException("The socket provider returned null socket");
+ }
return socket;
}
};
@@ -94,8 +97,9 @@ protected Properties parseQueryString(URI uri, Properties info) throws SQLExcept
// We need to convert port to string otherwise getProperty() will not see it.
urlProperties.setProperty(PROP_PORT, String.valueOf(uri.getPort()));
}
- if (info != null)
+ if (info != null) {
urlProperties.putAll(info);
+ }
// Validate properties.
int port;
@@ -128,7 +132,7 @@ protected SQLSocketProvider getSocketProviderInstance(String className) throws S
try {
Class> cls = Class.forName(className);
if (SQLSocketProvider.class.isAssignableFrom(cls)) {
- provider = (SQLSocketProvider)cls.newInstance();
+ provider = (SQLSocketProvider) cls.newInstance();
providerCache.put(className, provider);
}
} catch (Exception e) {
@@ -172,19 +176,19 @@ public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws
password.description = "password";
DriverPropertyInfo socketProvider = new DriverPropertyInfo(
- PROP_SOCKET_PROVIDER, properties.getProperty(PROP_SOCKET_PROVIDER));
+ PROP_SOCKET_PROVIDER, properties.getProperty(PROP_SOCKET_PROVIDER));
socketProvider.required = false;
socketProvider.description = "SocketProvider class implements org.tarantool.jdbc.SQLSocketProvider";
DriverPropertyInfo socketTimeout = new DriverPropertyInfo(
- PROP_SOCKET_TIMEOUT, properties.getProperty(PROP_SOCKET_TIMEOUT));
+ PROP_SOCKET_TIMEOUT, properties.getProperty(PROP_SOCKET_TIMEOUT));
socketTimeout.required = false;
socketTimeout.description = "The number of milliseconds to wait before a timeout is occurred on a socket" +
- " connect or read. The default value is 0, which means infinite timeout.";
+ " connect or read. The default value is 0, which means infinite timeout.";
- return new DriverPropertyInfo[]{host, port, user, password, socketProvider, socketTimeout};
+ return new DriverPropertyInfo[] { host, port, user, password, socketProvider, socketTimeout };
} catch (Exception e) {
throw new SQLException(e);
}
@@ -215,17 +219,19 @@ public Logger getParentLogger() throws SQLFeatureNotSupportedException {
* along with their sanitized values.
*
* @param props Connection properties.
+ *
* @return Comma-separated pairs of property names and values.
*/
protected static String diagProperties(Properties props) {
StringBuilder sb = new StringBuilder();
for (Map.Entry e : props.entrySet()) {
- if (sb.length() > 0)
+ if (sb.length() > 0) {
sb.append(", ");
+ }
sb.append(e.getKey());
sb.append('=');
- sb.append(PROP_USER.equals(e.getKey()) || PROP_PASSWORD.equals(e.getKey()) ?
- "*****" : e.getValue().toString());
+ sb.append((PROP_USER.equals(e.getKey()) || PROP_PASSWORD.equals(e.getKey()))
+ ? "*****" : e.getValue().toString());
}
return sb.toString();
}
diff --git a/src/main/java/org/tarantool/jdbc/SQLMsgPackLite.java b/src/main/java/org/tarantool/jdbc/SQLMsgPackLite.java
index 15068774..36fe82d8 100644
--- a/src/main/java/org/tarantool/jdbc/SQLMsgPackLite.java
+++ b/src/main/java/org/tarantool/jdbc/SQLMsgPackLite.java
@@ -1,13 +1,13 @@
package org.tarantool.jdbc;
+import org.tarantool.MsgPackLite;
+
import java.io.IOException;
import java.io.OutputStream;
import java.math.BigDecimal;
+import java.sql.Date;
import java.sql.Time;
import java.sql.Timestamp;
-import java.sql.Date;
-
-import org.tarantool.MsgPackLite;
public class SQLMsgPackLite extends MsgPackLite {
@@ -15,14 +15,14 @@ public class SQLMsgPackLite extends MsgPackLite {
@Override
public void pack(Object item, OutputStream os) throws IOException {
- if(item instanceof Date) {
- super.pack(((Date)item).getTime(), os);
- } else if(item instanceof Time) {
- super.pack(((Time)item).getTime(), os);
- } else if(item instanceof Timestamp) {
- super.pack(((Timestamp)item).getTime(), os);
- } else if(item instanceof BigDecimal) {
- super.pack(((BigDecimal)item).toPlainString(), os);
+ if (item instanceof Date) {
+ super.pack(((Date) item).getTime(), os);
+ } else if (item instanceof Time) {
+ super.pack(((Time) item).getTime(), os);
+ } else if (item instanceof Timestamp) {
+ super.pack(((Timestamp) item).getTime(), os);
+ } else if (item instanceof BigDecimal) {
+ super.pack(((BigDecimal) item).toPlainString(), os);
} else {
super.pack(item, os);
}
diff --git a/src/main/java/org/tarantool/jdbc/SQLPreparedStatement.java b/src/main/java/org/tarantool/jdbc/SQLPreparedStatement.java
index f0748c06..760aa569 100644
--- a/src/main/java/org/tarantool/jdbc/SQLPreparedStatement.java
+++ b/src/main/java/org/tarantool/jdbc/SQLPreparedStatement.java
@@ -26,7 +26,7 @@
public class SQLPreparedStatement extends SQLStatement implements PreparedStatement {
- final static String INVALID_CALL_MSG = "The method cannot be called on a PreparedStatement.";
+ static final String INVALID_CALL_MSG = "The method cannot be called on a PreparedStatement.";
final String sql;
final Map params;
@@ -54,6 +54,11 @@ public ResultSet executeQuery() throws SQLException {
return createResultSet(connection.executeQuery(sql, getParams()));
}
+ @Override
+ public ResultSet executeQuery(String sql) throws SQLException {
+ throw new SQLException(INVALID_CALL_MSG);
+ }
+
protected Object[] getParams() throws SQLException {
Object[] objects = new Object[params.size()];
for (int i = 1; i <= params.size(); i++) {
@@ -73,11 +78,21 @@ public int executeUpdate() throws SQLException {
return connection.executeUpdate(sql, getParams());
}
+ @Override
+ public int executeUpdate(String sql) throws SQLException {
+ throw new SQLException(INVALID_CALL_MSG);
+ }
+
@Override
public void setNull(int parameterIndex, int sqlType) throws SQLException {
setParameter(parameterIndex, null);
}
+ @Override
+ public void setNull(int parameterIndex, int sqlType, String typeName) throws SQLException {
+ setParameter(parameterIndex, null);
+ }
+
@Override
public void setBoolean(int parameterIndex, boolean parameterValue) throws SQLException {
setParameter(parameterIndex, parameterValue);
@@ -88,6 +103,11 @@ public void setByte(int parameterIndex, byte parameterValue) throws SQLException
setParameter(parameterIndex, parameterValue);
}
+ @Override
+ public void setBytes(int parameterIndex, byte[] parameterValue) throws SQLException {
+ setParameter(parameterIndex, parameterValue);
+ }
+
@Override
public void setShort(int parameterIndex, short parameterValue) throws SQLException {
setParameter(parameterIndex, parameterValue);
@@ -124,12 +144,12 @@ public void setString(int parameterIndex, String parameterValue) throws SQLExcep
}
@Override
- public void setBytes(int parameterIndex, byte[] parameterValue) throws SQLException {
+ public void setDate(int parameterIndex, Date parameterValue) throws SQLException {
setParameter(parameterIndex, parameterValue);
}
@Override
- public void setDate(int parameterIndex, Date parameterValue) throws SQLException {
+ public void setDate(int parameterIndex, Date parameterValue, Calendar calendar) throws SQLException {
setParameter(parameterIndex, parameterValue);
}
@@ -139,184 +159,176 @@ public void setTime(int parameterIndex, Time parameterValue) throws SQLException
}
@Override
- public void setTimestamp(int parameterIndex, Timestamp parameterValue) throws SQLException {
+ public void setTime(int parameterIndex, Time parameterValue, Calendar calendar) throws SQLException {
setParameter(parameterIndex, parameterValue);
}
@Override
- public void setAsciiStream(int parameterIndex, InputStream parameterValue, int length) throws SQLException {
+ public void setTimestamp(int parameterIndex, Timestamp parameterValue) throws SQLException {
setParameter(parameterIndex, parameterValue);
}
@Override
- public void setUnicodeStream(int parameterIndex, InputStream parameterValue, int length) throws SQLException {
+ public void setTimestamp(int parameterIndex, Timestamp parameterValue, Calendar calendar) throws SQLException {
setParameter(parameterIndex, parameterValue);
}
@Override
- public void setBinaryStream(int parameterIndex, InputStream parameterValue, int length) throws SQLException {
+ public void setAsciiStream(int parameterIndex, InputStream parameterValue, int length) throws SQLException {
setParameter(parameterIndex, parameterValue);
}
@Override
- public void clearParameters() throws SQLException {
- params.clear();
- }
-
- @Override
- public void setObject(int parameterIndex, Object x, int targetSqlType) throws SQLException {
- setObject(parameterIndex, x, targetSqlType, -1);
+ public void setAsciiStream(int parameterIndex, InputStream x) throws SQLException {
+ throw new SQLFeatureNotSupportedException();
}
@Override
- public void setObject(int parameterIndex, Object value) throws SQLException {
- setParameter(parameterIndex, value);
- }
-
- private void setParameter(int parameterIndex, Object value) throws SQLException {
- checkNotClosed();
- params.put(parameterIndex, value);
+ public void setAsciiStream(int parameterIndex, InputStream x, long length) throws SQLException {
+ throw new SQLFeatureNotSupportedException();
}
@Override
- public boolean execute() throws SQLException {
- checkNotClosed();
- discardLastResults();
- return handleResult(connection.execute(sql, getParams()));
+ public void setUnicodeStream(int parameterIndex, InputStream parameterValue, int length) throws SQLException {
+ setParameter(parameterIndex, parameterValue);
}
@Override
- public void setCharacterStream(int parameterIndex, Reader reader, int length) throws SQLException {
- throw new SQLFeatureNotSupportedException();
+ public void setBinaryStream(int parameterIndex, InputStream parameterValue, int length) throws SQLException {
+ setParameter(parameterIndex, parameterValue);
}
@Override
- public void setRef(int parameterIndex, Ref x) throws SQLException {
+ public void setBinaryStream(int parameterIndex, InputStream x, long length) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void setBlob(int parameterIndex, Blob x) throws SQLException {
+ public void setBinaryStream(int parameterIndex, InputStream x) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void setClob(int parameterIndex, Clob x) throws SQLException {
- throw new SQLFeatureNotSupportedException();
+ public void clearParameters() throws SQLException {
+ params.clear();
}
@Override
- public void setArray(int parameterIndex, Array x) throws SQLException {
- throw new SQLFeatureNotSupportedException();
+ public void setObject(int parameterIndex, Object x, int targetSqlType) throws SQLException {
+ setObject(parameterIndex, x, targetSqlType, -1);
}
@Override
- public ResultSetMetaData getMetaData() throws SQLException {
- return getResultSet().getMetaData();
+ public void setObject(int parameterIndex, Object value) throws SQLException {
+ setParameter(parameterIndex, value);
}
@Override
- public void setDate(int parameterIndex, Date parameterValue, Calendar calendar) throws SQLException {
+ public void setObject(int parameterIndex,
+ Object parameterValue,
+ int targetSqlType,
+ int scaleOrLength) throws SQLException {
setParameter(parameterIndex, parameterValue);
}
- @Override
- public void setTime(int parameterIndex, Time parameterValue, Calendar calendar) throws SQLException {
- setParameter(parameterIndex, parameterValue);
+ private void setParameter(int parameterIndex, Object value) throws SQLException {
+ checkNotClosed();
+ params.put(parameterIndex, value);
}
@Override
- public void setTimestamp(int parameterIndex, Timestamp parameterValue, Calendar calendar) throws SQLException {
- setParameter(parameterIndex, parameterValue);
+ public boolean execute() throws SQLException {
+ checkNotClosed();
+ return executeInternal(sql, getParams());
}
@Override
- public void setNull(int parameterIndex, int sqlType, String typeName) throws SQLException {
- setParameter(parameterIndex, null);
+ public boolean execute(String sql) throws SQLException {
+ throw new SQLException(INVALID_CALL_MSG);
}
@Override
- public void setURL(int parameterIndex, URL parameterValue) throws SQLException {
- setParameter(parameterIndex, parameterValue.toString());
+ public void setCharacterStream(int parameterIndex, Reader reader, int length) throws SQLException {
+ throw new SQLFeatureNotSupportedException();
}
@Override
- public ParameterMetaData getParameterMetaData() throws SQLException {
- return null;
+ public void setCharacterStream(int parameterIndex, Reader reader, long length) throws SQLException {
+ throw new SQLFeatureNotSupportedException();
}
@Override
- public void setRowId(int parameterIndex, RowId x) throws SQLException {
+ public void setCharacterStream(int parameterIndex, Reader reader) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void setNString(int parameterIndex, String parameterValue) throws SQLException {
- setParameter(parameterIndex, parameterValue);
+ public void setRef(int parameterIndex, Ref x) throws SQLException {
+ throw new SQLFeatureNotSupportedException();
}
@Override
- public void setNCharacterStream(int parameterIndex, Reader value, long length) throws SQLException {
+ public void setBlob(int parameterIndex, Blob x) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void setNClob(int parameterIndex, NClob value) throws SQLException {
+ public void setBlob(int parameterIndex, InputStream inputStream, long length) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void setClob(int parameterIndex, Reader reader, long length) throws SQLException {
+ public void setBlob(int parameterIndex, InputStream inputStream) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void setBlob(int parameterIndex, InputStream inputStream, long length) throws SQLException {
+ public void setClob(int parameterIndex, Clob x) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void setNClob(int parameterIndex, Reader reader, long length) throws SQLException {
+ public void setClob(int parameterIndex, Reader reader, long length) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void setSQLXML(int parameterIndex, SQLXML xmlObject) throws SQLException {
+ public void setClob(int parameterIndex, Reader reader) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void setObject(int parameterIndex, Object parameterValue, int targetSqlType, int scaleOrLength) throws SQLException {
- setParameter(parameterIndex, parameterValue);
+ public void setArray(int parameterIndex, Array x) throws SQLException {
+ throw new SQLFeatureNotSupportedException();
}
@Override
- public void setAsciiStream(int parameterIndex, InputStream x, long length) throws SQLException {
- throw new SQLFeatureNotSupportedException();
+ public ResultSetMetaData getMetaData() throws SQLException {
+ return getResultSet().getMetaData();
}
@Override
- public void setBinaryStream(int parameterIndex, InputStream x, long length) throws SQLException {
- throw new SQLFeatureNotSupportedException();
+ public void setURL(int parameterIndex, URL parameterValue) throws SQLException {
+ setParameter(parameterIndex, parameterValue.toString());
}
@Override
- public void setCharacterStream(int parameterIndex, Reader reader, long length) throws SQLException {
- throw new SQLFeatureNotSupportedException();
+ public ParameterMetaData getParameterMetaData() throws SQLException {
+ return null;
}
@Override
- public void setAsciiStream(int parameterIndex, InputStream x) throws SQLException {
+ public void setRowId(int parameterIndex, RowId x) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void setBinaryStream(int parameterIndex, InputStream x) throws SQLException {
- throw new SQLFeatureNotSupportedException();
+ public void setNString(int parameterIndex, String parameterValue) throws SQLException {
+ setParameter(parameterIndex, parameterValue);
}
@Override
- public void setCharacterStream(int parameterIndex, Reader reader) throws SQLException {
+ public void setNCharacterStream(int parameterIndex, Reader value, long length) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@@ -326,12 +338,12 @@ public void setNCharacterStream(int parameterIndex, Reader value) throws SQLExce
}
@Override
- public void setClob(int parameterIndex, Reader reader) throws SQLException {
+ public void setNClob(int parameterIndex, NClob value) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void setBlob(int parameterIndex, InputStream inputStream) throws SQLException {
+ public void setNClob(int parameterIndex, Reader reader, long length) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@@ -341,17 +353,12 @@ public void setNClob(int parameterIndex, Reader reader) throws SQLException {
}
@Override
- public void addBatch(String sql) throws SQLException {
- throw new SQLFeatureNotSupportedException();
- }
-
- @Override
- public int[] executeBatch() throws SQLException {
+ public void setSQLXML(int parameterIndex, SQLXML xmlObject) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void clearBatch() throws SQLException {
+ public void addBatch(String sql) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@@ -361,17 +368,13 @@ public void addBatch() throws SQLException {
}
@Override
- public ResultSet executeQuery(String sql) throws SQLException {
- throw new SQLException(INVALID_CALL_MSG);
+ public int[] executeBatch() throws SQLException {
+ throw new SQLFeatureNotSupportedException();
}
@Override
- public int executeUpdate(String sql) throws SQLException {
- throw new SQLException(INVALID_CALL_MSG);
+ public void clearBatch() throws SQLException {
+ throw new SQLFeatureNotSupportedException();
}
- @Override
- public boolean execute(String sql) throws SQLException {
- throw new SQLException(INVALID_CALL_MSG);
- }
}
diff --git a/src/main/java/org/tarantool/jdbc/SQLResultSet.java b/src/main/java/org/tarantool/jdbc/SQLResultSet.java
index f3a54566..de7a78ac 100644
--- a/src/main/java/org/tarantool/jdbc/SQLResultSet.java
+++ b/src/main/java/org/tarantool/jdbc/SQLResultSet.java
@@ -1,6 +1,10 @@
package org.tarantool.jdbc;
import org.tarantool.JDBCBridge;
+import org.tarantool.jdbc.cursor.CursorIterator;
+import org.tarantool.jdbc.cursor.InMemoryForwardCursorIteratorImpl;
+import org.tarantool.jdbc.cursor.InMemoryScrollableCursorIteratorImpl;
+import org.tarantool.util.SQLStates;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
@@ -28,61 +32,66 @@
import java.sql.Time;
import java.sql.Timestamp;
import java.util.Calendar;
+import java.util.LinkedHashMap;
import java.util.List;
-import java.util.ListIterator;
import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
-@SuppressWarnings("Since15")
public class SQLResultSet implements ResultSet {
- private ListIterator> iterator;
- private JDBCBridge bridge;
+ private final CursorIterator> iterator;
private final SQLResultSetMetaData metaData;
+ private Map columnByNameLookups;
+
private final Statement statement;
- private int maxRows;
- private List row = null;
+ private final int maxRows;
+
+ private AtomicBoolean isClosed = new AtomicBoolean(false);
- private final int type;
+ private final int scrollType;
private final int concurrencyLevel;
private final int holdability;
public SQLResultSet(JDBCBridge bridge, SQLStatement ownerStatement) throws SQLException {
- this.bridge = bridge;
- iterator = bridge.iterator();
- metaData = new SQLResultSetMetaData(bridge);
+ metaData = new SQLResultSetMetaData(bridge.getSqlMetadata());
statement = ownerStatement;
- type = statement.getResultSetType();
+ scrollType = statement.getResultSetType();
concurrencyLevel = statement.getResultSetConcurrency();
holdability = statement.getResultSetHoldability();
+ this.maxRows = statement.getMaxRows();
+
+ List> fetchedRows = bridge.getRows();
+ List> rows = maxRows == 0 || maxRows >= fetchedRows.size()
+ ? fetchedRows
+ : fetchedRows.subList(0, maxRows);
+
+ switch (scrollType) {
+ case ResultSet.TYPE_FORWARD_ONLY:
+ iterator = new InMemoryForwardCursorIteratorImpl(rows);
+ break;
+ case ResultSet.TYPE_SCROLL_INSENSITIVE:
+ iterator = new InMemoryScrollableCursorIteratorImpl(rows);
+ break;
+ default:
+ throw new SQLNonTransientException("", SQLStates.INVALID_PARAMETER_VALUE.getSqlState());
+ }
}
public int getMaxRows() {
return maxRows;
}
- public void setMaxRows(int maxRows) {
- this.maxRows = maxRows;
- }
-
- List getCurrentRow() {
- return row;
- }
-
- @Override
- public boolean next() throws SQLException {
+ public List getCurrentRow() throws SQLException {
checkNotClosed();
- if (iterator.hasNext() && (maxRows == 0 || iterator.nextIndex() < maxRows)) {
- row = iterator.next();
- return true;
- }
- row = null;
- return false;
+ return iterator.getItem();
}
@Override
public void close() throws SQLException {
-
+ if (isClosed.compareAndSet(false, true)) {
+ iterator.close();
+ }
}
@Override
@@ -90,42 +99,65 @@ public boolean wasNull() throws SQLException {
return false;
}
+ protected Object getRaw(int columnIndex) throws SQLException {
+ checkNotClosed();
+ metaData.checkColumnIndex(columnIndex);
+ List row = getCurrentRow();
+ return row.get(columnIndex - 1);
+ }
+
@Override
public String getString(int columnIndex) throws SQLException {
Object raw = getRaw(columnIndex);
return raw == null ? null : String.valueOf(raw);
}
- protected Object getRaw(int columnIndex) {
- return row.get(columnIndex - 1);
- }
-
- protected Integer getColumnIndex(String columnLabel) {
- return bridge.getColumnIndex(columnLabel);
+ @Override
+ public String getString(String columnLabel) throws SQLException {
+ return getString(findColumn(columnLabel));
}
-
@Override
public boolean getBoolean(int columnIndex) throws SQLException {
return Boolean.TRUE.equals(getRaw(columnIndex));
}
+ @Override
+ public boolean getBoolean(String columnLabel) throws SQLException {
+ return getBoolean(findColumn(columnLabel));
+ }
+
@Override
public byte getByte(int columnIndex) throws SQLException {
return (getNumber(columnIndex)).byteValue();
}
+ @Override
+ public byte getByte(String columnLabel) throws SQLException {
+ return getByte(findColumn(columnLabel));
+ }
+
@Override
public short getShort(int columnIndex) throws SQLException {
return (getNumber(columnIndex)).shortValue();
}
+ @Override
+ public short getShort(String columnLabel) throws SQLException {
+ return getShort(findColumn(columnLabel));
+ }
+
@Override
public int getInt(int columnIndex) throws SQLException {
return getNumber(columnIndex).intValue();
}
- private Number getNumber(int columnIndex) {
+ @Override
+ public int getInt(String columnLabel) throws SQLException {
+ return getInt(findColumn(columnLabel));
+ }
+
+ private Number getNumber(int columnIndex) throws SQLException {
Number raw = (Number) getRaw(columnIndex);
return raw == null ? 0 : raw;
}
@@ -135,16 +167,31 @@ public long getLong(int columnIndex) throws SQLException {
return (getNumber(columnIndex)).longValue();
}
+ @Override
+ public long getLong(String columnLabel) throws SQLException {
+ return getLong(findColumn(columnLabel));
+ }
+
@Override
public float getFloat(int columnIndex) throws SQLException {
return (getNumber(columnIndex)).floatValue();
}
+ @Override
+ public float getFloat(String columnLabel) throws SQLException {
+ return getFloat(findColumn(columnLabel));
+ }
+
@Override
public double getDouble(int columnIndex) throws SQLException {
return (getNumber(columnIndex)).doubleValue();
}
+ @Override
+ public double getDouble(String columnLabel) throws SQLException {
+ return getDouble(findColumn(columnLabel));
+ }
+
@Override
public BigDecimal getBigDecimal(int columnIndex, int scale) throws SQLException {
BigDecimal bigDecimal = new BigDecimal(getString(columnIndex));
@@ -152,263 +199,281 @@ public BigDecimal getBigDecimal(int columnIndex, int scale) throws SQLException
}
@Override
- public byte[] getBytes(int columnIndex) throws SQLException {
- return (byte[]) getRaw(columnIndex);
+ public BigDecimal getBigDecimal(String columnLabel, int scale) throws SQLException {
+ return getBigDecimal(findColumn(columnLabel));
}
@Override
- public Date getDate(int columnIndex) throws SQLException {
- return new java.sql.Date(getLong(columnIndex));
+ public BigDecimal getBigDecimal(int columnIndex) throws SQLException {
+ return getBigDecimal(columnIndex, -1);
}
@Override
- public Time getTime(int columnIndex) throws SQLException {
- return new java.sql.Time(getLong(columnIndex));
+ public BigDecimal getBigDecimal(String columnLabel) throws SQLException {
+ return getBigDecimal(columnLabel, -1);
}
@Override
- public Timestamp getTimestamp(int columnIndex) throws SQLException {
- return new java.sql.Timestamp(getLong(columnIndex));
+ public byte[] getBytes(int columnIndex) throws SQLException {
+ return (byte[]) getRaw(columnIndex);
}
@Override
- public InputStream getAsciiStream(int columnIndex) throws SQLException {
- return new ByteArrayInputStream(getString(columnIndex).getBytes(Charset.forName("ASCII")));
+ public byte[] getBytes(String columnLabel) throws SQLException {
+ return getBytes(findColumn(columnLabel));
}
@Override
- public InputStream getUnicodeStream(int columnIndex) throws SQLException {
- return new ByteArrayInputStream(getString(columnIndex).getBytes(Charset.forName("UTF-8")));
+ public Date getDate(int columnIndex) throws SQLException {
+ return new java.sql.Date(getLong(columnIndex));
}
@Override
- public InputStream getBinaryStream(int columnIndex) throws SQLException {
- return new ByteArrayInputStream(getBytes(columnIndex));
+ public Date getDate(String columnLabel) throws SQLException {
+ return getDate(findColumn(columnLabel));
}
@Override
- public String getString(String columnLabel) throws SQLException {
- return getString(getColumnIndex(columnLabel));
+ public Date getDate(int columnIndex, Calendar cal) throws SQLException {
+ throw new SQLFeatureNotSupportedException();
}
@Override
- public boolean getBoolean(String columnLabel) throws SQLException {
- return getBoolean(getColumnIndex(columnLabel));
+ public Date getDate(String columnLabel, Calendar cal) throws SQLException {
+ throw new SQLFeatureNotSupportedException();
}
@Override
- public byte getByte(String columnLabel) throws SQLException {
- return getByte(getColumnIndex(columnLabel));
+ public Time getTime(int columnIndex) throws SQLException {
+ return new java.sql.Time(getLong(columnIndex));
}
@Override
- public short getShort(String columnLabel) throws SQLException {
- return getShort(getColumnIndex(columnLabel));
+ public Time getTime(String columnLabel) throws SQLException {
+ return getTime(findColumn(columnLabel));
}
@Override
- public int getInt(String columnLabel) throws SQLException {
- return getInt(getColumnIndex(columnLabel));
+ public Time getTime(int columnIndex, Calendar cal) throws SQLException {
+ throw new SQLFeatureNotSupportedException();
}
@Override
- public long getLong(String columnLabel) throws SQLException {
- return getLong(getColumnIndex(columnLabel));
+ public Time getTime(String columnLabel, Calendar cal) throws SQLException {
+ throw new SQLFeatureNotSupportedException();
}
@Override
- public float getFloat(String columnLabel) throws SQLException {
- return getFloat(getColumnIndex(columnLabel));
+ public Timestamp getTimestamp(int columnIndex) throws SQLException {
+ return new java.sql.Timestamp(getLong(columnIndex));
}
@Override
- public double getDouble(String columnLabel) throws SQLException {
- return getDouble(getColumnIndex(columnLabel));
+ public Timestamp getTimestamp(String columnLabel) throws SQLException {
+ return getTimestamp(findColumn(columnLabel));
}
@Override
- public BigDecimal getBigDecimal(String columnLabel, int scale) throws SQLException {
- return getBigDecimal(getColumnIndex(columnLabel));
+ public Timestamp getTimestamp(int columnIndex, Calendar cal) throws SQLException {
+ throw new SQLFeatureNotSupportedException();
}
@Override
- public byte[] getBytes(String columnLabel) throws SQLException {
- return getBytes(getColumnIndex(columnLabel));
+ public Timestamp getTimestamp(String columnLabel, Calendar cal) throws SQLException {
+ throw new SQLFeatureNotSupportedException();
}
+
@Override
- public Date getDate(String columnLabel) throws SQLException {
- return getDate(getColumnIndex(columnLabel));
+ public InputStream getAsciiStream(int columnIndex) throws SQLException {
+ return new ByteArrayInputStream(getString(columnIndex).getBytes(Charset.forName("ASCII")));
}
@Override
- public Time getTime(String columnLabel) throws SQLException {
- return getTime(getColumnIndex(columnLabel));
+ public InputStream getAsciiStream(String columnLabel) throws SQLException {
+ return getAsciiStream(findColumn(columnLabel));
}
@Override
- public Timestamp getTimestamp(String columnLabel) throws SQLException {
- return getTimestamp(getColumnIndex(columnLabel));
+ public InputStream getUnicodeStream(int columnIndex) throws SQLException {
+ return new ByteArrayInputStream(getString(columnIndex).getBytes(Charset.forName("UTF-8")));
}
@Override
- public InputStream getAsciiStream(String columnLabel) throws SQLException {
- return getAsciiStream(getColumnIndex(columnLabel));
+ public InputStream getUnicodeStream(String columnLabel) throws SQLException {
+ return getUnicodeStream(findColumn(columnLabel));
}
@Override
- public InputStream getUnicodeStream(String columnLabel) throws SQLException {
- return getUnicodeStream(getColumnIndex(columnLabel));
+ public InputStream getBinaryStream(int columnIndex) throws SQLException {
+ return new ByteArrayInputStream(getBytes(columnIndex));
}
@Override
public InputStream getBinaryStream(String columnLabel) throws SQLException {
- return getBinaryStream(getColumnIndex(columnLabel));
+ return getBinaryStream(findColumn(columnLabel));
}
@Override
- public SQLWarning getWarnings() throws SQLException {
- return null;
+ public Reader getCharacterStream(int columnIndex) throws SQLException {
+ return new StringReader(getString(columnIndex));
}
@Override
- public void clearWarnings() throws SQLException {
+ public Reader getCharacterStream(String columnLabel) throws SQLException {
+ return new StringReader(getString(columnLabel));
+ }
+ @Override
+ public Object getObject(int columnIndex) throws SQLException {
+ return getRaw(columnIndex);
}
@Override
- public String getCursorName() throws SQLException {
+ public Object getObject(String columnLabel) throws SQLException {
+ return getRaw(findColumn(columnLabel));
+ }
+
+ @Override
+ public Object getObject(int columnIndex, Map> map) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public ResultSetMetaData getMetaData() throws SQLException {
- return metaData;
+ public Object getObject(String columnLabel, Map> map) throws SQLException {
+ throw new SQLFeatureNotSupportedException();
}
@Override
- public Object getObject(int columnIndex) throws SQLException {
- return getRaw(columnIndex);
+ public T getObject(int columnIndex, Class type) throws SQLException {
+ return type.cast(getRaw(columnIndex));
}
@Override
- public Object getObject(String columnLabel) throws SQLException {
- return getRaw(getColumnIndex(columnLabel));
+ public T getObject(String columnLabel, Class type) throws SQLException {
+ return type.cast(getRaw(findColumn(columnLabel)));
}
@Override
- public int findColumn(String columnLabel) throws SQLException {
- return getColumnIndex(columnLabel);
+ public SQLWarning getWarnings() throws SQLException {
+ return null;
}
@Override
- public Reader getCharacterStream(int columnIndex) throws SQLException {
- return new StringReader(getString(columnIndex));
+ public void clearWarnings() throws SQLException {
+
}
@Override
- public Reader getCharacterStream(String columnLabel) throws SQLException {
- return new StringReader(getString(columnLabel));
+ public String getCursorName() throws SQLException {
+ throw new SQLFeatureNotSupportedException();
}
@Override
- public BigDecimal getBigDecimal(int columnIndex) throws SQLException {
- return getBigDecimal(columnIndex, -1);
+ public ResultSetMetaData getMetaData() throws SQLException {
+ return metaData;
}
@Override
- public BigDecimal getBigDecimal(String columnLabel) throws SQLException {
- return getBigDecimal(columnLabel, -1);
+ public int findColumn(String columnLabel) throws SQLException {
+ return findColumnIndex(columnLabel);
+ }
+
+ protected int findColumnIndex(String columnLabel) throws SQLException {
+ if (columnByNameLookups == null) {
+ columnByNameLookups = new LinkedHashMap<>();
+ // Spec quote: Column labels supplied to getter methods are case insensitive.
+ // If a select list contains the same column more than once, the first instance
+ // of the column will be returned.
+ for (int i = metaData.getColumnCount(); i > 0; i--) {
+ columnByNameLookups.put(metaData.getColumnLabel(i).toUpperCase(), i);
+ }
+ }
+ return columnByNameLookups.getOrDefault(columnLabel.toUpperCase(), 0);
+ }
+
+ //region Cursor movement API
+
+ @Override
+ public boolean next() throws SQLException {
+ checkNotClosed();
+ return iterator.next();
}
@Override
public boolean isBeforeFirst() throws SQLException {
checkNotClosed();
- return row == null && iterator.previousIndex() == -1;
+ return iterator.isBeforeFirst();
}
@Override
public boolean isAfterLast() throws SQLException {
checkNotClosed();
- return iterator.nextIndex() == bridge.size() && row == null;
+ return iterator.isAfterLast();
}
@Override
public boolean isFirst() throws SQLException {
checkNotClosed();
- return iterator.previousIndex() == 0;
+ return iterator.isFirst();
}
@Override
public boolean isLast() throws SQLException {
checkNotClosed();
- return iterator.nextIndex() == bridge.size();
+ return iterator.isLast();
}
@Override
public void beforeFirst() throws SQLException {
checkNotClosed();
- row = null;
- iterator = bridge.iterator();
+ iterator.beforeFirst();
}
@Override
public void afterLast() throws SQLException {
checkNotClosed();
- while (next()) {
- }
+ iterator.afterLast();
}
@Override
public boolean first() throws SQLException {
- beforeFirst();
- return next();
+ checkNotClosed();
+ return iterator.first();
}
@Override
public boolean last() throws SQLException {
checkNotClosed();
- while (iterator.hasNext()) {
- next();
- }
- return row != null;
+ return iterator.last();
}
@Override
- public int getRow() throws SQLException {
+ public boolean absolute(int row) throws SQLException {
checkNotClosed();
- return iterator.previousIndex() + 1;
+ return iterator.absolute(row);
}
@Override
- public boolean absolute(int row) throws SQLException {
- beforeFirst();
- for (int i = 0; i < row && iterator.hasNext(); i++) {
- next();
- }
- return !(isAfterLast() || isBeforeFirst());
-
+ public boolean relative(int rows) throws SQLException {
+ checkNotClosed();
+ return iterator.relative(rows);
}
@Override
- public boolean relative(int rows) throws SQLException {
+ public boolean previous() throws SQLException {
checkNotClosed();
- for (int i = 0; i < rows && iterator.hasNext(); i++) {
- next();
- }
- return !(isAfterLast() || isBeforeFirst());
+ return iterator.previous();
}
@Override
- public boolean previous() throws SQLException {
+ public int getRow() throws SQLException {
checkNotClosed();
- if (iterator.hasPrevious()) {
- iterator.previous();
- return true;
- }
- return false;
+ return iterator.getRow();
}
+ //endregion
+
@Override
public void setFetchDirection(int direction) throws SQLException {
checkNotClosed();
@@ -436,7 +501,7 @@ public int getFetchSize() throws SQLException {
@Override
public int getType() throws SQLException {
checkNotClosed();
- return type;
+ return scrollType;
}
@Override
@@ -465,308 +530,329 @@ public void updateNull(int columnIndex) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
+ @Override
+ public void updateNull(String columnLabel) throws SQLException {
+ throw new SQLFeatureNotSupportedException();
+ }
+
@Override
public void updateBoolean(int columnIndex, boolean x) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
+ @Override
+ public void updateBoolean(String columnLabel, boolean x) throws SQLException {
+ throw new SQLFeatureNotSupportedException();
+ }
+
@Override
public void updateByte(int columnIndex, byte x) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
+ @Override
+ public void updateByte(String columnLabel, byte x) throws SQLException {
+ throw new SQLFeatureNotSupportedException();
+ }
+
@Override
public void updateShort(int columnIndex, short x) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void updateInt(int columnIndex, int x) throws SQLException {
+ public void updateShort(String columnLabel, short x) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void updateLong(int columnIndex, long x) throws SQLException {
+ public void updateInt(int columnIndex, int x) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void updateFloat(int columnIndex, float x) throws SQLException {
+ public void updateInt(String columnLabel, int x) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void updateDouble(int columnIndex, double x) throws SQLException {
+ public void updateLong(int columnIndex, long x) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void updateBigDecimal(int columnIndex, BigDecimal x) throws SQLException {
+ public void updateLong(String columnLabel, long x) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void updateString(int columnIndex, String x) throws SQLException {
+ public void updateFloat(int columnIndex, float x) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void updateBytes(int columnIndex, byte[] x) throws SQLException {
+ public void updateFloat(String columnLabel, float x) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void updateDate(int columnIndex, Date x) throws SQLException {
+ public void updateDouble(int columnIndex, double x) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void updateTime(int columnIndex, Time x) throws SQLException {
+ public void updateDouble(String columnLabel, double x) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void updateTimestamp(int columnIndex, Timestamp x) throws SQLException {
+ public void updateBigDecimal(int columnIndex, BigDecimal x) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void updateAsciiStream(int columnIndex, InputStream x, int length) throws SQLException {
+ public void updateBigDecimal(String columnLabel, BigDecimal x) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void updateBinaryStream(int columnIndex, InputStream x, int length) throws SQLException {
+ public void updateString(int columnIndex, String x) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void updateCharacterStream(int columnIndex, Reader x, int length) throws SQLException {
+ public void updateString(String columnLabel, String x) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void updateObject(int columnIndex, Object x, int scaleOrLength) throws SQLException {
+ public void updateBytes(int columnIndex, byte[] x) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void updateObject(int columnIndex, Object x) throws SQLException {
+ public void updateBytes(String columnLabel, byte[] x) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void updateNull(String columnLabel) throws SQLException {
+ public void updateDate(int columnIndex, Date x) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void updateBoolean(String columnLabel, boolean x) throws SQLException {
+ public void updateDate(String columnLabel, Date x) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void updateByte(String columnLabel, byte x) throws SQLException {
+ public void updateTime(int columnIndex, Time x) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void updateShort(String columnLabel, short x) throws SQLException {
+ public void updateTime(String columnLabel, Time x) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void updateInt(String columnLabel, int x) throws SQLException {
+ public void updateTimestamp(int columnIndex, Timestamp x) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void updateLong(String columnLabel, long x) throws SQLException {
+ public void updateTimestamp(String columnLabel, Timestamp x) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void updateFloat(String columnLabel, float x) throws SQLException {
+ public void updateAsciiStream(int columnIndex, InputStream x, int length) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void updateDouble(String columnLabel, double x) throws SQLException {
+ public void updateAsciiStream(String columnLabel, InputStream x, int length) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void updateBigDecimal(String columnLabel, BigDecimal x) throws SQLException {
+ public void updateAsciiStream(int columnIndex, InputStream x, long length) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void updateString(String columnLabel, String x) throws SQLException {
+ public void updateAsciiStream(String columnLabel, InputStream x, long length) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void updateBytes(String columnLabel, byte[] x) throws SQLException {
+ public void updateAsciiStream(int columnIndex, InputStream x) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void updateDate(String columnLabel, Date x) throws SQLException {
+ public void updateAsciiStream(String columnLabel, InputStream x) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void updateTime(String columnLabel, Time x) throws SQLException {
+ public void updateBinaryStream(int columnIndex, InputStream x, int length) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void updateTimestamp(String columnLabel, Timestamp x) throws SQLException {
+ public void updateBinaryStream(String columnLabel, InputStream x, int length) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void updateAsciiStream(String columnLabel, InputStream x, int length) throws SQLException {
+ public void updateBinaryStream(int columnIndex, InputStream x, long length) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void updateBinaryStream(String columnLabel, InputStream x, int length) throws SQLException {
+ public void updateBinaryStream(String columnLabel, InputStream x, long length) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void updateCharacterStream(String columnLabel, Reader reader, int length) throws SQLException {
+ public void updateBinaryStream(int columnIndex, InputStream x) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void updateObject(String columnLabel, Object x, int scaleOrLength) throws SQLException {
+ public void updateBinaryStream(String columnLabel, InputStream x) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void updateObject(String columnLabel, Object x) throws SQLException {
+ public void updateCharacterStream(int columnIndex, Reader x, int length) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void insertRow() throws SQLException {
+ public void updateCharacterStream(String columnLabel, Reader reader, int length) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void updateRow() throws SQLException {
+ public void updateCharacterStream(int columnIndex, Reader x, long length) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void deleteRow() throws SQLException {
+ public void updateCharacterStream(String columnLabel, Reader reader, long length) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void refreshRow() throws SQLException {
+ public void updateCharacterStream(int columnIndex, Reader x) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void cancelRowUpdates() throws SQLException {
+ public void updateCharacterStream(String columnLabel, Reader reader) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void moveToInsertRow() throws SQLException {
+ public void updateObject(int columnIndex, Object x, int scaleOrLength) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void moveToCurrentRow() throws SQLException {
+ public void updateObject(String columnLabel, Object x, int scaleOrLength) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public Statement getStatement() throws SQLException {
+ public void updateObject(int columnIndex, Object x) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public Object getObject(int columnIndex, Map> map) throws SQLException {
+ public void updateObject(String columnLabel, Object x) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public Ref getRef(int columnIndex) throws SQLException {
+ public void insertRow() throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public Blob getBlob(int columnIndex) throws SQLException {
+ public void updateRow() throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public Clob getClob(int columnIndex) throws SQLException {
+ public void deleteRow() throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public Array getArray(int columnIndex) throws SQLException {
+ public void refreshRow() throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public Object getObject(String columnLabel, Map> map) throws SQLException {
+ public void cancelRowUpdates() throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public Ref getRef(String columnLabel) throws SQLException {
+ public void moveToInsertRow() throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public Blob getBlob(String columnLabel) throws SQLException {
+ public void moveToCurrentRow() throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public Clob getClob(String columnLabel) throws SQLException {
+ public Statement getStatement() throws SQLException {
+ checkNotClosed();
+ return statement;
+ }
+
+ @Override
+ public Ref getRef(int columnIndex) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public Array getArray(String columnLabel) throws SQLException {
+ public Ref getRef(String columnLabel) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public Date getDate(int columnIndex, Calendar cal) throws SQLException {
+ public Blob getBlob(int columnIndex) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public Date getDate(String columnLabel, Calendar cal) throws SQLException {
+ public Blob getBlob(String columnLabel) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public Time getTime(int columnIndex, Calendar cal) throws SQLException {
+ public Clob getClob(int columnIndex) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public Time getTime(String columnLabel, Calendar cal) throws SQLException {
+ public Clob getClob(String columnLabel) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public Timestamp getTimestamp(int columnIndex, Calendar cal) throws SQLException {
+ public Array getArray(int columnIndex) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public Timestamp getTimestamp(String columnLabel, Calendar cal) throws SQLException {
+ public Array getArray(String columnLabel) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@@ -809,183 +895,113 @@ public void updateBlob(String columnLabel, Blob x) throws SQLException {
}
@Override
- public void updateClob(int columnIndex, Clob x) throws SQLException {
- throw new SQLFeatureNotSupportedException();
- }
-
- @Override
- public void updateClob(String columnLabel, Clob x) throws SQLException {
- throw new SQLFeatureNotSupportedException();
- }
-
- @Override
- public void updateArray(int columnIndex, Array x) throws SQLException {
- throw new SQLFeatureNotSupportedException();
- }
-
- @Override
- public void updateArray(String columnLabel, Array x) throws SQLException {
- throw new SQLFeatureNotSupportedException();
- }
-
- @Override
- public RowId getRowId(int columnIndex) throws SQLException {
- throw new SQLFeatureNotSupportedException();
- }
-
- @Override
- public RowId getRowId(String columnLabel) throws SQLException {
- throw new SQLFeatureNotSupportedException();
- }
-
- @Override
- public void updateRowId(int columnIndex, RowId x) throws SQLException {
- throw new SQLFeatureNotSupportedException();
- }
-
- @Override
- public void updateRowId(String columnLabel, RowId x) throws SQLException {
- throw new SQLFeatureNotSupportedException();
- }
-
- @Override
- public int getHoldability() throws SQLException {
- checkNotClosed();
- return holdability;
- }
-
- @Override
- public boolean isClosed() throws SQLException {
- return statement.isClosed();
- }
-
- @Override
- public void updateNString(int columnIndex, String nString) throws SQLException {
- throw new SQLFeatureNotSupportedException();
- }
-
- @Override
- public void updateNString(String columnLabel, String nString) throws SQLException {
- throw new SQLFeatureNotSupportedException();
- }
-
- @Override
- public void updateNClob(int columnIndex, NClob nClob) throws SQLException {
- throw new SQLFeatureNotSupportedException();
- }
-
- @Override
- public void updateNClob(String columnLabel, NClob nClob) throws SQLException {
- throw new SQLFeatureNotSupportedException();
- }
-
- @Override
- public NClob getNClob(int columnIndex) throws SQLException {
+ public void updateBlob(int columnIndex, InputStream inputStream, long length) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public NClob getNClob(String columnLabel) throws SQLException {
+ public void updateBlob(String columnLabel, InputStream inputStream, long length) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public SQLXML getSQLXML(int columnIndex) throws SQLException {
+ public void updateBlob(int columnIndex, InputStream inputStream) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public SQLXML getSQLXML(String columnLabel) throws SQLException {
+ public void updateBlob(String columnLabel, InputStream inputStream) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void updateSQLXML(int columnIndex, SQLXML xmlObject) throws SQLException {
+ public void updateClob(int columnIndex, Clob x) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void updateSQLXML(String columnLabel, SQLXML xmlObject) throws SQLException {
+ public void updateClob(String columnLabel, Clob x) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public String getNString(int columnIndex) throws SQLException {
+ public void updateClob(int columnIndex, Reader reader, long length) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public String getNString(String columnLabel) throws SQLException {
+ public void updateClob(String columnLabel, Reader reader, long length) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public Reader getNCharacterStream(int columnIndex) throws SQLException {
+ public void updateClob(int columnIndex, Reader reader) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public Reader getNCharacterStream(String columnLabel) throws SQLException {
+ public void updateClob(String columnLabel, Reader reader) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void updateNCharacterStream(int columnIndex, Reader x, long length) throws SQLException {
+ public void updateArray(int columnIndex, Array x) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void updateNCharacterStream(String columnLabel, Reader reader, long length) throws SQLException {
+ public void updateArray(String columnLabel, Array x) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void updateAsciiStream(int columnIndex, InputStream x, long length) throws SQLException {
+ public RowId getRowId(int columnIndex) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void updateBinaryStream(int columnIndex, InputStream x, long length) throws SQLException {
+ public RowId getRowId(String columnLabel) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void updateCharacterStream(int columnIndex, Reader x, long length) throws SQLException {
+ public void updateRowId(int columnIndex, RowId x) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void updateAsciiStream(String columnLabel, InputStream x, long length) throws SQLException {
+ public void updateRowId(String columnLabel, RowId x) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void updateBinaryStream(String columnLabel, InputStream x, long length) throws SQLException {
- throw new SQLFeatureNotSupportedException();
+ public int getHoldability() throws SQLException {
+ checkNotClosed();
+ return holdability;
}
@Override
- public void updateCharacterStream(String columnLabel, Reader reader, long length) throws SQLException {
- throw new SQLFeatureNotSupportedException();
+ public boolean isClosed() throws SQLException {
+ return isClosed.get() || statement.isClosed();
}
@Override
- public void updateBlob(int columnIndex, InputStream inputStream, long length) throws SQLException {
+ public void updateNString(int columnIndex, String string) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void updateBlob(String columnLabel, InputStream inputStream, long length) throws SQLException {
+ public void updateNString(String columnLabel, String string) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void updateClob(int columnIndex, Reader reader, long length) throws SQLException {
+ public void updateNClob(int columnIndex, NClob clob) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void updateClob(String columnLabel, Reader reader, long length) throws SQLException {
+ public void updateNClob(String columnLabel, NClob clob) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@@ -1000,102 +1016,107 @@ public void updateNClob(String columnLabel, Reader reader, long length) throws S
}
@Override
- public void updateNCharacterStream(int columnIndex, Reader x) throws SQLException {
+ public void updateNClob(int columnIndex, Reader reader) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void updateNCharacterStream(String columnLabel, Reader reader) throws SQLException {
+ public void updateNClob(String columnLabel, Reader reader) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void updateAsciiStream(int columnIndex, InputStream x) throws SQLException {
+ public NClob getNClob(int columnIndex) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void updateBinaryStream(int columnIndex, InputStream x) throws SQLException {
+ public NClob getNClob(String columnLabel) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void updateCharacterStream(int columnIndex, Reader x) throws SQLException {
+ public SQLXML getSQLXML(int columnIndex) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void updateAsciiStream(String columnLabel, InputStream x) throws SQLException {
+ public SQLXML getSQLXML(String columnLabel) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void updateBinaryStream(String columnLabel, InputStream x) throws SQLException {
+ public void updateSQLXML(int columnIndex, SQLXML xmlObject) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
-
@Override
- public void updateCharacterStream(String columnLabel, Reader reader) throws SQLException {
+ public void updateSQLXML(String columnLabel, SQLXML xmlObject) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void updateBlob(int columnIndex, InputStream inputStream) throws SQLException {
+ public String getNString(int columnIndex) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void updateBlob(String columnLabel, InputStream inputStream) throws SQLException {
+ public String getNString(String columnLabel) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void updateClob(int columnIndex, Reader reader) throws SQLException {
+ public Reader getNCharacterStream(int columnIndex) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void updateClob(String columnLabel, Reader reader) throws SQLException {
+ public Reader getNCharacterStream(String columnLabel) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void updateNClob(int columnIndex, Reader reader) throws SQLException {
+ public void updateNCharacterStream(int columnIndex, Reader x, long length) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public void updateNClob(String columnLabel, Reader reader) throws SQLException {
+ public void updateNCharacterStream(String columnLabel, Reader reader, long length) throws SQLException {
throw new SQLFeatureNotSupportedException();
}
@Override
- public T getObject(int columnIndex, Class type) throws SQLException {
- return type.cast(getRaw(columnIndex));
+ public void updateNCharacterStream(int columnIndex, Reader x) throws SQLException {
+ throw new SQLFeatureNotSupportedException();
}
@Override
- public T getObject(String columnLabel, Class type) throws SQLException {
- return type.cast(getRaw(getColumnIndex(columnLabel)));
+ public void updateNCharacterStream(String columnLabel, Reader reader) throws SQLException {
+ throw new SQLFeatureNotSupportedException();
}
@Override
- public T unwrap(Class iface) throws SQLException {
- throw new SQLFeatureNotSupportedException();
+ public T unwrap(Class type) throws SQLException {
+ if (isWrapperFor(type)) {
+ return type.cast(this);
+ }
+ throw new SQLNonTransientException("ResultSet does not wrap " + type.getName());
}
@Override
- public boolean isWrapperFor(Class> iface) throws SQLException {
- throw new SQLFeatureNotSupportedException();
+ public boolean isWrapperFor(Class> type) throws SQLException {
+ return type.isAssignableFrom(this.getClass());
}
@Override
public String toString() {
return "SQLResultSet{" +
- "metaData=" + metaData +
- ", row=" + row +
- '}';
+ "metaData=" + metaData +
+ ", statement=" + statement +
+ ", scrollType=" + scrollType +
+ ", concurrencyLevel=" + concurrencyLevel +
+ ", holdability=" + holdability +
+ '}';
}
protected void checkNotClosed() throws SQLException {
diff --git a/src/main/java/org/tarantool/jdbc/SQLResultSetMetaData.java b/src/main/java/org/tarantool/jdbc/SQLResultSetMetaData.java
index b3053d24..513679c9 100644
--- a/src/main/java/org/tarantool/jdbc/SQLResultSetMetaData.java
+++ b/src/main/java/org/tarantool/jdbc/SQLResultSetMetaData.java
@@ -1,22 +1,26 @@
package org.tarantool.jdbc;
+import org.tarantool.SqlProtoUtils;
+import org.tarantool.util.SQLStates;
+
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
+import java.sql.SQLNonTransientException;
import java.sql.Types;
-
-import org.tarantool.JDBCBridge;
+import java.util.List;
public class SQLResultSetMetaData implements ResultSetMetaData {
- protected final JDBCBridge JDBCBridgeExecutor;
- public SQLResultSetMetaData(JDBCBridge JDBCBridgeExecutor) {
- this.JDBCBridgeExecutor = JDBCBridgeExecutor;
+ private final List sqlMetadata;
+
+ public SQLResultSetMetaData(List sqlMetaData) {
+ this.sqlMetadata = sqlMetaData;
}
@Override
public int getColumnCount() throws SQLException {
- return JDBCBridgeExecutor.getColumnCount();
+ return sqlMetadata.size();
}
@Override
@@ -56,12 +60,14 @@ public int getColumnDisplaySize(int column) throws SQLException {
@Override
public String getColumnLabel(int column) throws SQLException {
- return JDBCBridgeExecutor.getColumnName(column);
+ checkColumnIndex(column);
+ return sqlMetadata.get(column - 1).getName();
}
@Override
public String getColumnName(int column) throws SQLException {
- return JDBCBridgeExecutor.getColumnName(column);
+ checkColumnIndex(column);
+ return sqlMetadata.get(column - 1).getName();
}
@Override
@@ -120,19 +126,31 @@ public String getColumnClassName(int column) throws SQLException {
}
@Override
- public T unwrap(Class iface) throws SQLException {
- throw new SQLFeatureNotSupportedException();
+ public T unwrap(Class type) throws SQLException {
+ if (isWrapperFor(type)) {
+ return type.cast(this);
+ }
+ throw new SQLNonTransientException("ResultSetMetadata does not wrap " + type.getName());
}
@Override
- public boolean isWrapperFor(Class> iface) throws SQLException {
- throw new SQLFeatureNotSupportedException();
+ public boolean isWrapperFor(Class> type) throws SQLException {
+ return type.isAssignableFrom(this.getClass());
+ }
+
+ void checkColumnIndex(int columnIndex) throws SQLException {
+ if (columnIndex < 1 || columnIndex > getColumnCount()) {
+ throw new SQLNonTransientException(
+ String.format("Column index %d is out of range. Max index is %d", columnIndex, getColumnCount()),
+ SQLStates.INVALID_PARAMETER_VALUE.getSqlState()
+ );
+ }
}
@Override
public String toString() {
return "SQLResultSetMetaData{" +
- "bridge=" + JDBCBridgeExecutor +
+ "sqlMetadata=" + sqlMetadata +
'}';
}
}
diff --git a/src/main/java/org/tarantool/jdbc/SQLStatement.java b/src/main/java/org/tarantool/jdbc/SQLStatement.java
index 658d2487..a4dfdaf1 100644
--- a/src/main/java/org/tarantool/jdbc/SQLStatement.java
+++ b/src/main/java/org/tarantool/jdbc/SQLStatement.java
@@ -1,6 +1,8 @@
package org.tarantool.jdbc;
import org.tarantool.JDBCBridge;
+import org.tarantool.util.JdbcConstants;
+import org.tarantool.util.SQLStates;
import java.sql.Connection;
import java.sql.ResultSet;
@@ -9,20 +11,33 @@
import java.sql.SQLNonTransientException;
import java.sql.SQLWarning;
import java.sql.Statement;
-
-@SuppressWarnings("Since15")
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Tarantool {@link Statement} implementation.
+ *
+ * Supports {@link ResultSet#TYPE_FORWARD_ONLY} and {@link ResultSet#TYPE_SCROLL_INSENSITIVE}
+ * types of cursors.
+ * Supports only {@link ResultSet#HOLD_CURSORS_OVER_COMMIT} holdability type.
+ */
public class SQLStatement implements Statement {
protected final SQLConnection connection;
+ /**
+ * Current result set / update count associated to this statement.
+ */
private SQLResultSet resultSet;
+ private int updateCount;
+
private final int resultSetType;
private final int resultSetConcurrency;
private final int resultSetHoldability;
- private int updateCount;
private int maxRows;
+ private final AtomicBoolean isClosed = new AtomicBoolean(false);
+
protected SQLStatement(SQLConnection sqlConnection) throws SQLException {
this.connection = sqlConnection;
this.resultSetType = ResultSet.TYPE_FORWARD_ONLY;
@@ -43,20 +58,50 @@ protected SQLStatement(SQLConnection sqlConnection,
@Override
public ResultSet executeQuery(String sql) throws SQLException {
checkNotClosed();
- discardLastResults();
- return createResultSet(connection.executeQuery(sql));
+ if (!executeInternal(sql)) {
+ throw new SQLException("No results were returned", SQLStates.NO_DATA.getSqlState());
+ }
+ return resultSet;
}
@Override
public int executeUpdate(String sql) throws SQLException {
checkNotClosed();
- discardLastResults();
- return connection.executeUpdate(sql);
+ if (executeInternal(sql)) {
+ throw new SQLException(
+ "Result was returned but nothing was expected",
+ SQLStates.TOO_MANY_RESULTS.getSqlState()
+ );
+ }
+ return updateCount;
}
@Override
- public void close() throws SQLException {
+ public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException {
+ checkNotClosed();
+ JdbcConstants.checkGeneratedKeysConstant(autoGeneratedKeys);
+ if (autoGeneratedKeys != Statement.NO_GENERATED_KEYS) {
+ throw new SQLFeatureNotSupportedException();
+ }
+ return executeUpdate(sql);
+ }
+
+ @Override
+ public int executeUpdate(String sql, int[] columnIndexes) throws SQLException {
+ throw new SQLFeatureNotSupportedException();
+ }
+
+ @Override
+ public int executeUpdate(String sql, String[] columnNames) throws SQLException {
+ throw new SQLFeatureNotSupportedException();
+ }
+ @Override
+ public void close() throws SQLException {
+ if (isClosed.compareAndSet(false, true)) {
+ cancel();
+ discardLastResults();
+ }
}
@Override
@@ -82,9 +127,6 @@ public void setMaxRows(int maxRows) throws SQLException {
throw new SQLNonTransientException("Max rows parameter can't be a negative value");
}
this.maxRows = maxRows;
- if (resultSet != null) {
- resultSet.setMaxRows(this.maxRows);
- }
}
@Override
@@ -126,27 +168,39 @@ public void setCursorName(String name) throws SQLException {
public boolean execute(String sql) throws SQLException {
checkNotClosed();
discardLastResults();
- return handleResult(connection.execute(sql));
+ return executeInternal(sql);
}
@Override
- public ResultSet getResultSet() throws SQLException {
+ public boolean execute(String sql, int autoGeneratedKeys) throws SQLException {
checkNotClosed();
- try {
- return resultSet;
- } finally {
- resultSet = null;
+ JdbcConstants.checkGeneratedKeysConstant(autoGeneratedKeys);
+ if (autoGeneratedKeys != Statement.NO_GENERATED_KEYS) {
+ throw new SQLFeatureNotSupportedException();
}
+ return execute(sql);
+ }
+
+ @Override
+ public boolean execute(String sql, int[] columnIndexes) throws SQLException {
+ throw new SQLFeatureNotSupportedException();
+ }
+
+ @Override
+ public boolean execute(String sql, String[] columnNames) throws SQLException {
+ throw new SQLFeatureNotSupportedException();
+ }
+
+ @Override
+ public ResultSet getResultSet() throws SQLException {
+ checkNotClosed();
+ return resultSet;
}
@Override
public int getUpdateCount() throws SQLException {
checkNotClosed();
- try {
- return updateCount;
- } finally {
- updateCount = -1;
- }
+ return updateCount;
}
@Override
@@ -155,6 +209,12 @@ public boolean getMoreResults() throws SQLException {
return false;
}
+ @Override
+ public boolean getMoreResults(int current) throws SQLException {
+ checkNotClosed();
+ return false;
+ }
+
@Override
public void setFetchDirection(int direction) throws SQLException {
checkNotClosed();
@@ -212,45 +272,10 @@ public Connection getConnection() throws SQLException {
return connection;
}
- @Override
- public boolean getMoreResults(int current) throws SQLException {
- checkNotClosed();
- return false;
- }
-
@Override
public ResultSet getGeneratedKeys() throws SQLException {
- throw new SQLFeatureNotSupportedException();
- }
-
- @Override
- public int executeUpdate(String sql, int autoGeneratedKeys) throws SQLException {
- throw new SQLFeatureNotSupportedException();
- }
-
- @Override
- public int executeUpdate(String sql, int[] columnIndexes) throws SQLException {
- throw new SQLFeatureNotSupportedException();
- }
-
- @Override
- public int executeUpdate(String sql, String[] columnNames) throws SQLException {
- throw new SQLFeatureNotSupportedException();
- }
-
- @Override
- public boolean execute(String sql, int autoGeneratedKeys) throws SQLException {
- throw new SQLFeatureNotSupportedException();
- }
-
- @Override
- public boolean execute(String sql, int[] columnIndexes) throws SQLException {
- throw new SQLFeatureNotSupportedException();
- }
-
- @Override
- public boolean execute(String sql, String[] columnNames) throws SQLException {
- throw new SQLFeatureNotSupportedException();
+ checkNotClosed();
+ return new SQLResultSet(JDBCBridge.EMPTY, this);
}
@Override
@@ -261,7 +286,7 @@ public int getResultSetHoldability() throws SQLException {
@Override
public boolean isClosed() throws SQLException {
- return connection.isClosed();
+ return isClosed.get() || connection.isClosed();
}
@Override
@@ -301,7 +326,8 @@ public boolean isWrapperFor(Class> type) throws SQLException {
/**
* Clears the results of the most recent execution.
*/
- protected void discardLastResults() {
+ protected void discardLastResults() throws SQLException {
+ clearWarnings();
updateCount = -1;
if (resultSet != null) {
try {
@@ -313,16 +339,29 @@ protected void discardLastResults() {
}
}
+ /**
+ * Performs query execution.
+ *
+ * @param sql query
+ * @param params optional params
+ *
+ * @return {@code true}, if the result is a ResultSet object;
+ */
+ protected boolean executeInternal(String sql, Object... params) throws SQLException {
+ discardLastResults();
+ return handleResult(connection.execute(sql, params));
+ }
+
/**
* Sets the internals according to the result of last execution.
*
* @param result The result of SQL statement execution.
+ *
* @return {@code true}, if the result is a ResultSet object.
*/
protected boolean handleResult(Object result) throws SQLException {
if (result instanceof JDBCBridge) {
resultSet = createResultSet((JDBCBridge) result);
- resultSet.setMaxRows(maxRows);
updateCount = -1;
return true;
} else {
@@ -333,10 +372,12 @@ protected boolean handleResult(Object result) throws SQLException {
}
/**
- * Returns {@link ResultSet} which will be initialized by data
+ * Returns {@link ResultSet} which will be initialized by data
.
*
* @param data predefined result to be wrapped by {@link ResultSet}
+ *
* @return wrapped result
+ *
* @throws SQLException if a database access error occurs or
* this method is called on a closed Statement
*/
@@ -354,4 +395,5 @@ protected void checkNotClosed() throws SQLException {
throw new SQLNonTransientException("Statement is closed.");
}
}
+
}
diff --git a/src/main/java/org/tarantool/jdbc/cursor/CursorIterator.java b/src/main/java/org/tarantool/jdbc/cursor/CursorIterator.java
new file mode 100644
index 00000000..7c29080a
--- /dev/null
+++ b/src/main/java/org/tarantool/jdbc/cursor/CursorIterator.java
@@ -0,0 +1,40 @@
+package org.tarantool.jdbc.cursor;
+
+import java.sql.SQLException;
+
+/**
+ * Extracted interface for a cursor traversal part of {@link java.sql.ResultSet}.
+ */
+public interface CursorIterator {
+
+ boolean isBeforeFirst() throws SQLException;
+
+ boolean isAfterLast() throws SQLException;
+
+ boolean isFirst() throws SQLException;
+
+ boolean isLast() throws SQLException;
+
+ void beforeFirst() throws SQLException;
+
+ void afterLast() throws SQLException;
+
+ boolean first() throws SQLException;
+
+ boolean last() throws SQLException;
+
+ boolean absolute(int row) throws SQLException;
+
+ boolean relative(int rows) throws SQLException;
+
+ boolean next() throws SQLException;
+
+ boolean previous() throws SQLException;
+
+ int getRow() throws SQLException;
+
+ T getItem() throws SQLException;
+
+ void close();
+
+}
diff --git a/src/main/java/org/tarantool/jdbc/cursor/InMemoryForwardCursorIteratorImpl.java b/src/main/java/org/tarantool/jdbc/cursor/InMemoryForwardCursorIteratorImpl.java
new file mode 100644
index 00000000..1b735968
--- /dev/null
+++ b/src/main/java/org/tarantool/jdbc/cursor/InMemoryForwardCursorIteratorImpl.java
@@ -0,0 +1,137 @@
+package org.tarantool.jdbc.cursor;
+
+import org.tarantool.util.SQLStates;
+
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Forward only iterator to support {@link java.sql.ResultSet#TYPE_FORWARD_ONLY}
+ * result set semantic.
+ */
+public class InMemoryForwardCursorIteratorImpl implements CursorIterator> {
+
+ protected final List> results = new ArrayList<>();
+ protected int currentPosition = -1;
+
+ public InMemoryForwardCursorIteratorImpl(List> results) {
+ if (results == null) {
+ throw new IllegalArgumentException("Results list cannot be null");
+ }
+ this.results.addAll(results);
+ }
+
+ @Override
+ public boolean isBeforeFirst() throws SQLException {
+ return hasResults() && currentPosition == -1;
+ }
+
+ @Override
+ public boolean isAfterLast() throws SQLException {
+ return hasResults() && currentPosition == results.size();
+ }
+
+ @Override
+ public boolean isFirst() throws SQLException {
+ return hasResults() && currentPosition == 0;
+ }
+
+ @Override
+ public boolean isLast() throws SQLException {
+ return hasResults() && currentPosition == results.size() - 1;
+ }
+
+ @Override
+ public void beforeFirst() throws SQLException {
+ throw new SQLException(
+ "Cannot be called on forward only cursor",
+ SQLStates.INVALID_CURSOR_STATE.getSqlState()
+ );
+ }
+
+ @Override
+ public void afterLast() throws SQLException {
+ throw new SQLException(
+ "Cannot be called on forward only cursor",
+ SQLStates.INVALID_CURSOR_STATE.getSqlState()
+ );
+ }
+
+ @Override
+ public boolean first() throws SQLException {
+ throw new SQLException(
+ "Cannot be called on forward only cursor",
+ SQLStates.INVALID_CURSOR_STATE.getSqlState()
+ );
+ }
+
+ @Override
+ public boolean last() throws SQLException {
+ throw new SQLException(
+ "Cannot be called on forward only cursor",
+ SQLStates.INVALID_CURSOR_STATE.getSqlState()
+ );
+ }
+
+ @Override
+ public boolean absolute(int row) throws SQLException {
+ throw new SQLException(
+ "Cannot be called on forward only cursor",
+ SQLStates.INVALID_CURSOR_STATE.getSqlState()
+ );
+ }
+
+ @Override
+ public boolean relative(int rows) throws SQLException {
+ throw new SQLException(
+ "Cannot be called on forward only cursor",
+ SQLStates.INVALID_CURSOR_STATE.getSqlState()
+ );
+ }
+
+ @Override
+ public boolean next() throws SQLException {
+ if (!hasResults() || isAfterLast()) {
+ return false;
+ }
+ currentPosition++;
+ return !isAfterLast();
+ }
+
+ @Override
+ public boolean previous() throws SQLException {
+ throw new SQLException(
+ "Cannot be called on forward only cursor",
+ SQLStates.INVALID_CURSOR_STATE.getSqlState()
+ );
+ }
+
+ @Override
+ public int getRow() throws SQLException {
+ return !hasResults() || isBeforeFirst() || isAfterLast() ? 0 : currentPosition + 1;
+ }
+
+ @Override
+ public List getItem() throws SQLException {
+ int row = getRow();
+ if (row > 0) {
+ return results.get(row - 1);
+ }
+ throw new SQLException(
+ "Cursor is out of range. Try to call next() or previous() before.",
+ SQLStates.INVALID_CURSOR_STATE.getSqlState()
+ );
+ }
+
+ protected boolean hasResults() {
+ return !results.isEmpty();
+ }
+
+ @Override
+ public void close() {
+ results.clear();
+ currentPosition = -1;
+ }
+
+}
diff --git a/src/main/java/org/tarantool/jdbc/cursor/InMemoryScrollableCursorIteratorImpl.java b/src/main/java/org/tarantool/jdbc/cursor/InMemoryScrollableCursorIteratorImpl.java
new file mode 100644
index 00000000..0561cc37
--- /dev/null
+++ b/src/main/java/org/tarantool/jdbc/cursor/InMemoryScrollableCursorIteratorImpl.java
@@ -0,0 +1,100 @@
+package org.tarantool.jdbc.cursor;
+
+import java.sql.SQLException;
+import java.util.List;
+
+/**
+ * Scrollable iterator to support {@link java.sql.ResultSet#TYPE_SCROLL_INSENSITIVE}
+ * result set type semantic.
+ */
+public class InMemoryScrollableCursorIteratorImpl extends InMemoryForwardCursorIteratorImpl {
+
+ public InMemoryScrollableCursorIteratorImpl(List> results) {
+ super(results);
+ }
+
+ @Override
+ public void beforeFirst() throws SQLException {
+ moveIfHasResults(-1);
+ }
+
+ @Override
+ public void afterLast() throws SQLException {
+ moveIfHasResults(results.size());
+ }
+
+ @Override
+ public boolean first() throws SQLException {
+ return moveIfHasResults(0);
+ }
+
+ @Override
+ public boolean last() throws SQLException {
+ return moveIfHasResults(results.size() - 1);
+ }
+
+ @Override
+ public boolean absolute(int row) throws SQLException {
+ if (!hasResults()) {
+ return false;
+ }
+ if (row == 0) {
+ beforeFirst();
+ return false;
+ }
+ if (row > results.size()) {
+ afterLast();
+ return false;
+ }
+ if (row < -results.size()) {
+ beforeFirst();
+ return false;
+ }
+
+ currentPosition = (row > 0) ? row - 1 : results.size() + row;
+ return true;
+ }
+
+ @Override
+ public boolean relative(int rows) throws SQLException {
+ if (!hasResults()) {
+ return false;
+ }
+ if (rows == 0) {
+ return !(isBeforeFirst() || isAfterLast());
+ }
+ if (currentPosition + rows >= results.size()) {
+ afterLast();
+ return false;
+ }
+ if (currentPosition + rows <= -1) {
+ beforeFirst();
+ return false;
+ }
+
+ return absolute(currentPosition + rows + 1);
+ }
+
+ @Override
+ public boolean previous() throws SQLException {
+ if (!hasResults() || isBeforeFirst()) {
+ return false;
+ }
+ currentPosition--;
+ return !isBeforeFirst();
+ }
+
+ /**
+ * Moves to the target position if results is not empty.
+ *
+ * @param position target position
+ * @return successful operation status
+ */
+ private boolean moveIfHasResults(int position) {
+ if (!hasResults()) {
+ return false;
+ }
+ currentPosition = position;
+ return true;
+ }
+}
diff --git a/src/main/java/org/tarantool/protocol/ByteBufferBackedInputStream.java b/src/main/java/org/tarantool/protocol/ByteBufferBackedInputStream.java
index 2ff9df44..b2f83090 100644
--- a/src/main/java/org/tarantool/protocol/ByteBufferBackedInputStream.java
+++ b/src/main/java/org/tarantool/protocol/ByteBufferBackedInputStream.java
@@ -4,13 +4,15 @@
import java.nio.ByteBuffer;
/**
- * Input stream based on ByteBuffer
+ * Input stream based on ByteBuffer.
*/
class ByteBufferBackedInputStream extends InputStream {
private final ByteBuffer buf;
/**
+ * Constructs a new wrapper-stream for {@link ByteBuffer}.
+ *
* @param buf a buffer that have to be ready for read (flipped)
*/
public ByteBufferBackedInputStream(ByteBuffer buf) {
diff --git a/src/main/java/org/tarantool/protocol/ProtoUtils.java b/src/main/java/org/tarantool/protocol/ProtoUtils.java
index a1ba90ee..a0724279 100644
--- a/src/main/java/org/tarantool/protocol/ProtoUtils.java
+++ b/src/main/java/org/tarantool/protocol/ProtoUtils.java
@@ -16,6 +16,7 @@
import java.net.SocketAddress;
import java.net.SocketException;
import java.nio.ByteBuffer;
+import java.nio.channels.NonReadableChannelException;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SocketChannel;
import java.security.MessageDigest;
@@ -26,15 +27,20 @@
import java.util.Map;
public abstract class ProtoUtils {
- private final static int DEFAULT_INITIAL_REQUEST_SIZE = 4096;
+
+ public static final int LENGTH_OF_SIZE_MESSAGE = 5;
+
+ private static final int DEFAULT_INITIAL_REQUEST_SIZE = 4096;
private static final String WELCOME = "Tarantool ";
/**
- * Reads tarantool binary protocol's packet from {@code inputStream}
+ * Reads tarantool binary protocol's packet from {@code inputStream}.
*
* @param inputStream ready to use input stream
- * @return Nonnull instance of packet.
- * @throws IOException in case of any io-error.
+ *
+ * @return Nonnull instance of packet
+ *
+ * @throws IOException in case of any io-error
*/
public static TarantoolPacket readPacket(InputStream inputStream) throws IOException {
CountInputStreamImpl msgStream = new CountInputStreamImpl(inputStream);
@@ -52,13 +58,71 @@ public static TarantoolPacket readPacket(InputStream inputStream) throws IOExcep
return new TarantoolPacket(headers, body);
}
+ /**
+ * Reads a tarantool's binary protocol packet from the reader.
+ *
+ * @param bufferReader readable channel that have to be in blocking mode
+ * or instance of {@link ReadableViaSelectorChannel}
+ *
+ * @return tarantool binary protocol message wrapped by instance of {@link TarantoolPacket}
+ *
+ * @throws IOException if any IO-error occurred during read from the channel
+ * @throws CommunicationException input stream bytes constitute msg pack message in wrong format
+ * @throws NonReadableChannelException If this channel was not opened for reading
+ */
+ public static TarantoolPacket readPacket(ReadableByteChannel bufferReader)
+ throws CommunicationException, IOException {
+
+ ByteBuffer buffer = ByteBuffer.allocate(LENGTH_OF_SIZE_MESSAGE);
+ bufferReader.read(buffer);
+
+ buffer.flip();
+ int size = ((Number) getMsgPackLite().unpack(new ByteBufferBackedInputStream(buffer))).intValue();
+
+ buffer = ByteBuffer.allocate(size);
+ bufferReader.read(buffer);
+
+ buffer.flip();
+ ByteBufferBackedInputStream msgBytesStream = new ByteBufferBackedInputStream(buffer);
+ Object unpackedHeaders = getMsgPackLite().unpack(msgBytesStream);
+ if (!(unpackedHeaders instanceof Map)) {
+ //noinspection ConstantConditions
+ throw new CommunicationException(
+ "Error while unpacking headers of tarantool response: " +
+ "expected type Map but was " +
+ unpackedHeaders != null ? unpackedHeaders.getClass().toString() : "null"
+ );
+ }
+ //noinspection unchecked (checked above)
+ Map headers = (Map) unpackedHeaders;
+
+ Map body = null;
+ if (msgBytesStream.hasAvailable()) {
+ Object unpackedBody = getMsgPackLite().unpack(msgBytesStream);
+ if (!(unpackedBody instanceof Map)) {
+ //noinspection ConstantConditions
+ throw new CommunicationException(
+ "Error while unpacking body of tarantool response: " +
+ "expected type Map but was " +
+ unpackedBody != null ? unpackedBody.getClass().toString() : "null"
+ );
+ }
+ //noinspection unchecked (checked above)
+ body = (Map) unpackedBody;
+ }
+
+ return new TarantoolPacket(headers, body);
+ }
+
/**
* Connects to a tarantool node described by {@code socket}. Performs an authentication if required
*
* @param socket a socket channel to a tarantool node
* @param username auth username
* @param password auth password
+ *
* @return object with information about a connection/
+ *
* @throws IOException in case of any IO fails
* @throws CommunicationException when welcome string is invalid
* @throws TarantoolException in case of failed authentication
@@ -97,7 +161,9 @@ public static TarantoolGreeting connect(Socket socket,
* @param channel a socket channel to tarantool node. The channel have to be in blocking mode
* @param username auth username
* @param password auth password
+ *
* @return object with information about a connection/
+ *
* @throws IOException in case of any IO fails
* @throws CommunicationException when welcome string is invalid
* @throws TarantoolException in case of failed authentication
@@ -110,7 +176,7 @@ public static TarantoolGreeting connect(SocketChannel channel,
String firstLine = new String(welcomeBytes.array());
assertCorrectWelcome(firstLine, channel.getRemoteAddress());
- String serverVersion = firstLine.substring(WELCOME.length());
+ final String serverVersion = firstLine.substring(WELCOME.length());
welcomeBytes.clear();
channel.read(welcomeBytes);
@@ -129,8 +195,10 @@ public static TarantoolGreeting connect(SocketChannel channel,
private static void assertCorrectWelcome(String firstLine, SocketAddress remoteAddress) {
if (!firstLine.startsWith(WELCOME)) {
- String errMsg = "Failed to connect to node " + remoteAddress.toString() + ":" +
- " Welcome message should starts with tarantool but starts with '" + firstLine + "'";
+ String errMsg = "Failed to connect to node " + remoteAddress.toString() +
+ ": Welcome message should starts with tarantool but starts with '" +
+ firstLine +
+ "'";
throw new CommunicationException(errMsg, new IllegalStateException("Invalid welcome packet"));
}
}
@@ -158,58 +226,6 @@ public static void writeFully(SocketChannel channel, ByteBuffer buffer) throws I
}
}
- public static final int LENGTH_OF_SIZE_MESSAGE = 5;
-
- /**
- * Reads a tarantool's binary protocol packet from the reader
- *
- * @param bufferReader readable channel that have to be in blocking mode
- * or instance of {@link ReadableViaSelectorChannel}
- * @return tarantool binary protocol message wrapped by instance of {@link TarantoolPacket}.
- * @throws IOException if any IO-error occurred during read from the channel
- * @throws CommunicationException input stream bytes consitute msg pack message in wrong format.
- * @throws java.nio.channels.NonReadableChannelException – If this channel was not opened for reading
- */
- public static TarantoolPacket readPacket(ReadableByteChannel bufferReader)
- throws CommunicationException, IOException {
-
- ByteBuffer buffer = ByteBuffer.allocate(LENGTH_OF_SIZE_MESSAGE);
- bufferReader.read(buffer);
-
- buffer.flip();
- int size = ((Number) getMsgPackLite().unpack(new ByteBufferBackedInputStream(buffer))).intValue();
-
- buffer = ByteBuffer.allocate(size);
- bufferReader.read(buffer);
-
- buffer.flip();
- ByteBufferBackedInputStream msgBytesStream = new ByteBufferBackedInputStream(buffer);
- Object unpackedHeaders = getMsgPackLite().unpack(msgBytesStream);
- if (!(unpackedHeaders instanceof Map)) {
- //noinspection ConstantConditions
- throw new CommunicationException("Error while unpacking headers of tarantool response: " +
- "expected type Map but was " +
- unpackedHeaders != null ? unpackedHeaders.getClass().toString() : "null");
- }
- //noinspection unchecked (checked above)
- Map headers = (Map) unpackedHeaders;
-
- Map body = null;
- if (msgBytesStream.hasAvailable()) {
- Object unpackedBody = getMsgPackLite().unpack(msgBytesStream);
- if (!(unpackedBody instanceof Map)) {
- //noinspection ConstantConditions
- throw new CommunicationException("Error while unpacking body of tarantool response: " +
- "expected type Map but was " +
- unpackedBody != null ? unpackedBody.getClass().toString() : "null");
- }
- //noinspection unchecked (checked above)
- body = (Map) unpackedBody;
- }
-
- return new TarantoolPacket(headers, body);
- }
-
public static ByteBuffer createAuthPacket(String username,
final String password,
String salt) throws IOException {
@@ -237,7 +253,7 @@ public static ByteBuffer createAuthPacket(String username,
auth.add(p);
return createPacket(DEFAULT_INITIAL_REQUEST_SIZE, Code.AUTH, 0L, null,
- Key.USER_NAME, username, Key.TUPLE, auth);
+ Key.USER_NAME, username, Key.TUPLE, auth);
}
public static ByteBuffer createPacket(Code code, Long syncId, Long schemaId, Object... args) throws IOException {
@@ -260,7 +276,7 @@ public static ByteBuffer createPacket(int initialRequestSize,
Object... args) throws IOException {
ByteArrayOutputStream bos = new ByteArrayOutputStream(initialRequestSize);
bos.write(new byte[5]);
- DataOutputStream ds = new DataOutputStream(bos);
+ final DataOutputStream ds = new DataOutputStream(bos);
Map header = new EnumMap<>(Key.class);
Map body = new EnumMap<>(Key.class);
header.put(Key.CODE, code);
diff --git a/src/main/java/org/tarantool/protocol/ReadableViaSelectorChannel.java b/src/main/java/org/tarantool/protocol/ReadableViaSelectorChannel.java
index 2a7a623d..f6565ba5 100644
--- a/src/main/java/org/tarantool/protocol/ReadableViaSelectorChannel.java
+++ b/src/main/java/org/tarantool/protocol/ReadableViaSelectorChannel.java
@@ -31,7 +31,9 @@ public ReadableViaSelectorChannel(SocketChannel channel) throws IOException {
@Override
public int read(ByteBuffer buffer) throws IOException {
- int count, n;
+ int count;
+ int n;
+
count = n = channel.read(buffer);
if (n < 0) {
diff --git a/src/main/java/org/tarantool/protocol/TarantoolPacket.java b/src/main/java/org/tarantool/protocol/TarantoolPacket.java
index d8c30707..ca131177 100644
--- a/src/main/java/org/tarantool/protocol/TarantoolPacket.java
+++ b/src/main/java/org/tarantool/protocol/TarantoolPacket.java
@@ -23,9 +23,11 @@ public Long getCode() {
if (!(potenticalCode instanceof Long)) {
//noinspection ConstantConditions
- throw new IllegalStateException("A value contained in the header by key '" + Key.CODE.name() + "'" +
- " is not instance of Long class: " +
- potenticalCode != null ? potenticalCode.getClass().toString() : "null");
+ throw new IllegalStateException(
+ "A value contained in the header by key '" + Key.CODE.name() +
+ "' is not instance of Long class: " +
+ potenticalCode != null ? potenticalCode.getClass().toString() : "null"
+ );
}
return (Long) potenticalCode;
diff --git a/src/main/java/org/tarantool/util/JdbcConstants.java b/src/main/java/org/tarantool/util/JdbcConstants.java
new file mode 100644
index 00000000..dfbad572
--- /dev/null
+++ b/src/main/java/org/tarantool/util/JdbcConstants.java
@@ -0,0 +1,24 @@
+package org.tarantool.util;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.SQLNonTransientException;
+import java.sql.Statement;
+
+public class JdbcConstants {
+
+ public static void checkGeneratedKeysConstant(int autoGeneratedKeys) throws SQLException {
+ if (autoGeneratedKeys != Statement.NO_GENERATED_KEYS &&
+ autoGeneratedKeys != Statement.RETURN_GENERATED_KEYS) {
+ throw new SQLNonTransientException("", SQLStates.INVALID_PARAMETER_VALUE.getSqlState());
+ }
+ }
+
+ public static void checkHoldabilityConstant(int holdability) throws SQLException {
+ if (holdability != ResultSet.CLOSE_CURSORS_AT_COMMIT &&
+ holdability != ResultSet.HOLD_CURSORS_OVER_COMMIT) {
+ throw new SQLNonTransientException("", SQLStates.INVALID_PARAMETER_VALUE.getSqlState());
+ }
+ }
+
+}
diff --git a/src/main/java/org/tarantool/util/SQLStates.java b/src/main/java/org/tarantool/util/SQLStates.java
index 48f7f332..89ac309d 100644
--- a/src/main/java/org/tarantool/util/SQLStates.java
+++ b/src/main/java/org/tarantool/util/SQLStates.java
@@ -2,8 +2,12 @@
public enum SQLStates {
+ TOO_MANY_RESULTS("0100E"),
+ NO_DATA("02000"),
+ CONNECTION_DOES_NOT_EXIST("08003"),
INVALID_PARAMETER_VALUE("22023"),
- CONNECTION_DOES_NOT_EXIST("08003");
+ INVALID_CURSOR_STATE("24000"),
+ INVALID_TRANSACTION_STATE("25000");
private final String sqlState;
diff --git a/src/main/java/org/tarantool/util/StringUtils.java b/src/main/java/org/tarantool/util/StringUtils.java
new file mode 100644
index 00000000..7a289a3f
--- /dev/null
+++ b/src/main/java/org/tarantool/util/StringUtils.java
@@ -0,0 +1,21 @@
+package org.tarantool.util;
+
+public class StringUtils {
+
+ public static boolean isEmpty(String string) {
+ return (string == null) || (string.isEmpty());
+ }
+
+ public static boolean isNotEmpty(String string) {
+ return !isEmpty(string);
+ }
+
+ public static boolean isBlank(String string) {
+ return (string == null) || (string.trim().isEmpty());
+ }
+
+ public static boolean isNotBlank(String string) {
+ return !isBlank(string);
+ }
+
+}
diff --git a/src/test/java/org/tarantool/AbstractAsyncClientOperationsIT.java b/src/test/java/org/tarantool/AbstractAsyncClientOperationsIT.java
index 8813c0ce..ec94eeb7 100644
--- a/src/test/java/org/tarantool/AbstractAsyncClientOperationsIT.java
+++ b/src/test/java/org/tarantool/AbstractAsyncClientOperationsIT.java
@@ -1,5 +1,11 @@
package org.tarantool;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -14,8 +20,6 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
-import static org.junit.jupiter.api.Assertions.*;
-
/**
* Abstract class with test cases for asynchronous operations provided by getOps() method of a child class.
*/
@@ -87,8 +91,9 @@ public void testOperations() throws ExecutionException, InterruptedException, Ti
futs.add(ops.call("box.space.basic_test:delete", Collections.singletonList(30)));
// Wait completion of all operations.
- for (Future> f : futs)
+ for (Future> f : futs) {
f.get(TIMEOUT, TimeUnit.MILLISECONDS);
+ }
// Check the effects.
checkTupleResult(consoleSelect(SPACE_NAME, 10), Arrays.asList(10, "ten"));
diff --git a/src/test/java/org/tarantool/AbstractSocketProviderTest.java b/src/test/java/org/tarantool/AbstractSocketProviderTest.java
new file mode 100644
index 00000000..526e55b8
--- /dev/null
+++ b/src/test/java/org/tarantool/AbstractSocketProviderTest.java
@@ -0,0 +1,50 @@
+package org.tarantool;
+
+import static org.mockito.Mockito.anyObject;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.nio.channels.SocketChannel;
+import java.util.Collection;
+import java.util.stream.Collectors;
+
+public class AbstractSocketProviderTest {
+
+ protected String extractRawHostAndPortString(SocketAddress socketAddress) {
+ InetSocketAddress inetSocketAddress = (InetSocketAddress) socketAddress;
+ return inetSocketAddress.getAddress().getHostName() + ":" + inetSocketAddress.getPort();
+ }
+
+ protected Iterable asRawHostAndPort(Collection addresses) {
+ return addresses.stream()
+ .map(this::extractRawHostAndPortString)
+ .collect(Collectors.toList());
+ }
+
+ protected T wrapWithMockChannelProvider(T source) throws IOException {
+ T wrapper = spy(source);
+ doReturn(makeSocketChannel()).when(wrapper).openChannel(anyObject());
+ return wrapper;
+ }
+
+ protected T wrapWithMockErroredChannelProvider(T source) throws IOException {
+ T wrapper = spy(source);
+ doThrow(IOException.class).when(wrapper).openChannel(anyObject());
+ return wrapper;
+ }
+
+ private SocketChannel makeSocketChannel() {
+ SocketChannel socketChannel = mock(SocketChannel.class);
+ when(socketChannel.socket()).thenReturn(mock(Socket.class));
+
+ return socketChannel;
+ }
+
+}
diff --git a/src/test/java/org/tarantool/AbstractTarantoolConnectorIT.java b/src/test/java/org/tarantool/AbstractTarantoolConnectorIT.java
index 739a1cf3..950bd131 100644
--- a/src/test/java/org/tarantool/AbstractTarantoolConnectorIT.java
+++ b/src/test/java/org/tarantool/AbstractTarantoolConnectorIT.java
@@ -1,11 +1,16 @@
package org.tarantool;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.tarantool.TestUtils.makeInstanceEnv;
+
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.opentest4j.AssertionFailedError;
-import java.math.BigInteger;
import java.io.IOException;
+import java.math.BigInteger;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.List;
@@ -15,16 +20,11 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-import static org.tarantool.TestUtils.makeInstanceEnv;
-
/**
* Abstract test. Provides environment control and frequently used functions.
*/
public abstract class AbstractTarantoolConnectorIT {
+
protected static final String host = System.getProperty("tntHost", "localhost");
protected static final int port = Integer.parseInt(System.getProperty("tntPort", "3301"));
protected static final int consolePort = Integer.parseInt(System.getProperty("tntConsolePort", "3313"));
@@ -37,8 +37,8 @@ public abstract class AbstractTarantoolConnectorIT {
protected static final int TIMEOUT = 500;
protected static final int RESTART_TIMEOUT = 2000;
- protected static final SocketChannelProvider socketChannelProvider = new TestSocketChannelProvider(host, port,
- RESTART_TIMEOUT);
+ protected static final SocketChannelProvider socketChannelProvider =
+ new TestSocketChannelProvider(host, port, RESTART_TIMEOUT);
protected static TarantoolControl control;
protected static TarantoolConsole console;
@@ -124,7 +124,7 @@ private static void executeLua(String[] exprs) {
protected void checkTupleResult(Object res, List tuple) {
assertNotNull(res);
assertTrue(List.class.isAssignableFrom(res.getClass()));
- List list = (List)res;
+ List list = (List) res;
assertEquals(1, list.size());
assertNotNull(list.get(0));
assertTrue(List.class.isAssignableFrom(list.get(0).getClass()));
@@ -143,19 +143,19 @@ protected static TarantoolClientConfig makeClientConfig() {
return fillClientConfig(new TarantoolClientConfig());
}
- protected static TarantoolClusterClientConfig makeClusterClientConfig() {
+ public static TarantoolClusterClientConfig makeClusterClientConfig() {
TarantoolClusterClientConfig config = fillClientConfig(new TarantoolClusterClientConfig());
config.executor = null;
config.operationExpiryTimeMillis = TIMEOUT;
return config;
}
- private static T fillClientConfig(TarantoolClientConfig config) {
+ private static T fillClientConfig(T config) {
config.username = username;
config.password = password;
config.initTimeoutMillis = RESTART_TIMEOUT;
config.sharedBufferSize = 128;
- return (T)config;
+ return (T) config;
}
protected static TarantoolConsole openConsole() {
@@ -163,7 +163,7 @@ protected static TarantoolConsole openConsole() {
}
protected static TarantoolConsole openConsole(String instance) {
- return TarantoolConsole.open(control.tntCtlWorkDir, instance);
+ return TarantoolConsole.open(TarantoolControl.tntCtlWorkDir, instance);
}
protected TarantoolConnection openConnection() {
@@ -194,8 +194,9 @@ private void appendKey(StringBuilder sb, Object key) {
if (List.class.isAssignableFrom(key.getClass())) {
List parts = (List) key;
for (int i = 0; i < parts.size(); i++) {
- if (i != 0)
+ if (i != 0) {
sb.append(", ");
+ }
Object k = parts.get(i);
if (k instanceof BigInteger) {
appendBigInteger(sb, (BigInteger) k);
@@ -234,12 +235,10 @@ protected List> consoleDelete(String spaceName, Object key) {
protected static void stopTarantool(String instance) {
control.stop(instance);
- control.waitStopped("jdk-testing");
}
protected static void startTarantool(String instance) {
control.start(instance);
- control.waitStarted("jdk-testing");
}
/**
@@ -247,7 +246,7 @@ protected static void startTarantool(String instance) {
*
* @param timeout Timeout in ms.
* @param message Error message.
- * @param r Runnable.
+ * @param r Runnable.
*/
protected void assertTimeoutPreemptively(int timeout, String message, Runnable r) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
@@ -264,4 +263,5 @@ protected void assertTimeoutPreemptively(int timeout, String message, Runnable r
executorService.shutdownNow();
}
}
+
}
diff --git a/src/test/java/org/tarantool/AbstractTarantoolOpsIT.java b/src/test/java/org/tarantool/AbstractTarantoolOpsIT.java
index 5576a338..b39b4d75 100644
--- a/src/test/java/org/tarantool/AbstractTarantoolOpsIT.java
+++ b/src/test/java/org/tarantool/AbstractTarantoolOpsIT.java
@@ -1,5 +1,11 @@
package org.tarantool;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.tarantool.TestAssumptions.assumeMaximalServerVersion;
+import static org.tarantool.TestAssumptions.assumeMinimalServerVersion;
+
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.function.Executable;
@@ -8,10 +14,6 @@
import java.util.Collections;
import java.util.List;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-
/**
* Tests operations available in {@link TarantoolClientOps} interface.
*/
@@ -172,8 +174,7 @@ public void testUpdateMultiPart() {
Arrays.asList("=", 2, "thirty"));
}
- private void checkUpdate(String space, int spaceId, List key, List initTuple, List expectedTuple,
- Object ... ops) {
+ private void checkUpdate(String space, int spaceId, List key, List initTuple, List expectedTuple, Object... ops) {
// Try update non-existing key.
List> res = getOps().update(spaceId, key, ops);
assertNotNull(res);
@@ -214,8 +215,7 @@ public void testUpsertMultiPart() {
Arrays.asList("=", 2, "fourty"));
}
- private void checkUpsert(String space, int spaceId, List key, List defTuple, List expectedTuple,
- Object ... ops) {
+ private void checkUpsert(String space, int spaceId, List key, List defTuple, List expectedTuple, Object... ops) {
// Check that key doesn't exist.
assertEquals(Collections.emptyList(), consoleSelect(space, key));
@@ -373,23 +373,38 @@ public void execute() throws Throwable {
}
@Test
- public void testInsertInvalidData() {
+ void testInsertInvalidData() {
// Invalid types.
- TarantoolException ex = assertThrows(TarantoolException.class, new Executable() {
- @Override
- public void execute() throws Throwable {
- getOps().insert(SPACE_ID, Arrays.asList("one", 1));
- }
- });
+ TarantoolException ex = assertThrows(
+ TarantoolException.class,
+ () -> getOps().insert(SPACE_ID, Arrays.asList("one", 1))
+ );
assertEquals("Tuple field 1 type does not match one required by operation: expected integer", ex.getMessage());
+ }
+
+ @Test
+ public void testInsertInvalidTupleSize2xVersion() {
+ assumeMinimalServerVersion(console, ServerVersion.V_2_1);
// Invalid tuple size.
- ex = assertThrows(TarantoolException.class, new Executable() {
- @Override
- public void execute() throws Throwable {
- getOps().insert(SPACE_ID, Collections.singletonList(101));
- }
- });
+ TarantoolException ex = assertThrows(
+ TarantoolException.class,
+ () -> getOps().insert(SPACE_ID, Collections.singletonList(101))
+ );
assertEquals("Tuple field 2 required by space format is missing", ex.getMessage());
}
+
+ @Test
+ public void testInsertInvalidTupleSize1xVersion() {
+ assumeMaximalServerVersion(console, ServerVersion.V_1_10);
+
+ // Invalid tuple size.
+ TarantoolException ex = assertThrows(
+ TarantoolException.class,
+ () -> getOps().insert(SPACE_ID, Collections.singletonList(101))
+ );
+ assertEquals("Tuple field count 1 is less than required by space format or defined indexes " +
+ "(expected at least 2)", ex.getMessage());
+ }
+
}
diff --git a/src/test/java/org/tarantool/AbstractTarantoolSQLConnectorIT.java b/src/test/java/org/tarantool/AbstractTarantoolSQLConnectorIT.java
new file mode 100644
index 00000000..46805575
--- /dev/null
+++ b/src/test/java/org/tarantool/AbstractTarantoolSQLConnectorIT.java
@@ -0,0 +1,117 @@
+package org.tarantool;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.tarantool.TestUtils.makeInstanceEnv;
+
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Abstract test. Provides environment control and frequently used functions which are related to SQL.
+ */
+public abstract class AbstractTarantoolSQLConnectorIT {
+
+ protected static final String HOST = System.getProperty("tntHost", "localhost");
+ protected static final int PORT = Integer.parseInt(System.getProperty("tntPort", "3301"));
+ protected static final int CONSOLE_PORT = Integer.parseInt(System.getProperty("tntConsolePort", "3313"));
+ protected static final String USERNAME = System.getProperty("tntUser", "test_admin");
+ protected static final String PASSWORD = System.getProperty("tntPass", "4pWBZmLEgkmKK5WP");
+
+ protected static final String LUA_FILE = "jdk-testing.lua";
+ protected static final int LISTEN = 3301;
+ protected static final int ADMIN = 3313;
+ protected static final int RESTART_TIMEOUT = 2000;
+
+ protected static final SocketChannelProvider socketChannelProvider = new TestSocketChannelProvider(
+ HOST, PORT, RESTART_TIMEOUT
+ );
+
+ protected static TarantoolControl control;
+ protected static TarantoolConsole console;
+
+ @BeforeAll
+ public static void setupEnv() {
+ control = new TarantoolControl();
+ control.createInstance("jdk-testing", LUA_FILE, makeInstanceEnv(LISTEN, ADMIN));
+ startTarantool("jdk-testing");
+ console = openConsole();
+ }
+
+ @AfterAll
+ public static void cleanupEnv() {
+ try {
+ console.close();
+ } finally {
+ stopTarantool("jdk-testing");
+ }
+ }
+
+ protected static void executeLua(String[] exprs) {
+ for (String expr : exprs) {
+ console.exec(expr);
+ }
+ }
+
+ protected void checkTupleResult(List> expected, List> actual) {
+ assertNotNull(expected);
+ assertEquals(expected, actual);
+ }
+
+ protected List> asResult(Object[][] tuples) {
+ List> result = new ArrayList<>();
+ if (tuples != null) {
+ for (int i = 0; i < tuples.length; i++) {
+ Object[] tuple = tuples[i];
+ if (tuple.length % 2 != 0) {
+ continue;
+ }
+ Map row = new HashMap<>();
+ for (int j = 0; j <= tuple.length / 2; j += 2) {
+ row.put(tuple[j].toString(), tuple[j + 1]);
+ }
+ result.add(row);
+ }
+ }
+ return result;
+ }
+
+ protected TarantoolClient makeClient() {
+ return new TarantoolClientImpl(socketChannelProvider, makeClientConfig());
+ }
+
+ protected TarantoolClient makeClient(SocketChannelProvider provider) {
+ return new TarantoolClientImpl(provider, makeClientConfig());
+ }
+
+ protected static TarantoolClientConfig makeClientConfig() {
+ TarantoolClientConfig config = new TarantoolClientConfig();
+ config.username = USERNAME;
+ config.password = PASSWORD;
+ config.initTimeoutMillis = RESTART_TIMEOUT;
+ config.sharedBufferSize = 128;
+ return config;
+ }
+
+ protected static TarantoolConsole openConsole() {
+ return TarantoolConsole.open(HOST, CONSOLE_PORT);
+ }
+
+ protected static TarantoolConsole openConsole(String instance) {
+ return TarantoolConsole.open(control.tntCtlWorkDir, instance);
+ }
+
+ protected static void stopTarantool(String instance) {
+ control.stop(instance);
+ }
+
+ protected static void startTarantool(String instance) {
+ control.start(instance);
+ }
+
+}
diff --git a/src/test/java/org/tarantool/AbstractTarantoolSQLOpsIT.java b/src/test/java/org/tarantool/AbstractTarantoolSQLOpsIT.java
new file mode 100644
index 00000000..2e84a857
--- /dev/null
+++ b/src/test/java/org/tarantool/AbstractTarantoolSQLOpsIT.java
@@ -0,0 +1,86 @@
+package org.tarantool;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.tarantool.TestAssumptions.assumeMinimalServerVersion;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Tests operations available in {@link TarantoolSQLOps} interface.
+ */
+public abstract class AbstractTarantoolSQLOpsIT extends AbstractTarantoolSQLConnectorIT {
+
+ private static final String[] SETUP_SCRIPT = new String[] {
+ "\\set language sql",
+
+ "CREATE TABLE sql_test (id INTEGER PRIMARY KEY, val VARCHAR(100));",
+ "CREATE UNIQUE INDEX sql_test_val_index_unique ON sql_test (val);",
+
+ "INSERT INTO sql_test VALUES (1, 'A');",
+ "INSERT INTO sql_test VALUES (2, 'B');",
+ "INSERT INTO sql_test VALUES (3, 'C');"
+ };
+
+ private static final String[] CLEAN_SCRIPT = new String[] {
+ "DROP TABLE sql_test;",
+ "\\set language lua"
+ };
+
+ protected abstract TarantoolSQLOps>> getSQLOps();
+
+ @BeforeEach
+ void setUpTest() {
+ assumeMinimalServerVersion(console, ServerVersion.V_2_1);
+ executeLua(SETUP_SCRIPT);
+ }
+
+ @AfterEach
+ void tearDownTest() {
+ executeLua(CLEAN_SCRIPT);
+ }
+
+ @Test
+ public void testSelectOne() {
+ List> result = getSQLOps().query("SELECT id, val FROM sql_test WHERE id = 1");
+ checkTupleResult(
+ asResult(new Object[][] { {"ID", 1, "VAL", "A"} }),
+ result
+ );
+ }
+
+ @Test
+ public void testSelectMany() {
+ List> result = getSQLOps().query("SELECT id, val FROM sql_test WHERE id = 1 or val = 'B'");
+ checkTupleResult(
+ asResult(new Object[][] { {"ID", 1, "VAL", "A"}, {"ID", 2, "VAL", "B"} }),
+ result
+ );
+ }
+
+ @Test
+ public void testSelectEmpty() {
+ List> result = getSQLOps().query("SELECT id, val FROM sql_test WHERE val = 'AB'");
+ checkTupleResult(
+ asResult(new Object[][] { }),
+ result
+ );
+ }
+
+ @Test
+ public void testInsertOneRecord() {
+ Long rowsAffected = getSQLOps().update("INSERT INTO sql_test VALUES (27, 'Z');");
+ assertEquals(1L, (long) rowsAffected);
+ }
+
+ @Test
+ public void testInsertDuplication() {
+ assertThrows(TarantoolException.class, () -> getSQLOps().update("INSERT INTO sql_test VALUES (1, 'A');"));
+ }
+
+}
diff --git a/src/test/java/org/tarantool/ClientComposableAsyncOpsIT.java b/src/test/java/org/tarantool/ClientComposableAsyncOpsIT.java
index b1d75e31..04d0059e 100644
--- a/src/test/java/org/tarantool/ClientComposableAsyncOpsIT.java
+++ b/src/test/java/org/tarantool/ClientComposableAsyncOpsIT.java
@@ -9,11 +9,14 @@
*/
public class ClientComposableAsyncOpsIT extends AbstractAsyncClientOperationsIT {
- private static class Composable2FutureClientOpsAdapter implements TarantoolClientOps, Object, Future>> {
+ private static class Composable2FutureClientOpsAdapter
+ implements TarantoolClientOps, Object, Future>> {
private final TarantoolClientOps, Object, CompletionStage>> originOps;
- private Composable2FutureClientOpsAdapter(TarantoolClientOps, Object, CompletionStage>> originOps) {
+ private Composable2FutureClientOpsAdapter(
+ TarantoolClientOps, Object, CompletionStage>> originOps) {
+
this.originOps = originOps;
}
@@ -23,7 +26,12 @@ public Future> select(Integer space, Integer index, List> key, int off
}
@Override
- public Future> select(Integer space, Integer index, List> key, int offset, int limit, Iterator iterator) {
+ public Future> select(Integer space,
+ Integer index,
+ List> key,
+ int offset,
+ int limit,
+ Iterator iterator) {
return originOps.select(space, index, key, offset, limit, iterator).toCompletableFuture();
}
diff --git a/src/test/java/org/tarantool/ClientOperationsIT.java b/src/test/java/org/tarantool/ClientOperationsIT.java
index 22d1965a..f752bd48 100644
--- a/src/test/java/org/tarantool/ClientOperationsIT.java
+++ b/src/test/java/org/tarantool/ClientOperationsIT.java
@@ -1,5 +1,8 @@
package org.tarantool;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -7,9 +10,6 @@
import java.util.List;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-
/**
* Tests for synchronous operations of {@link TarantoolClientImpl} class.
*
diff --git a/src/test/java/org/tarantool/ClientReconnectClusterIT.java b/src/test/java/org/tarantool/ClientReconnectClusterIT.java
index 35737a32..fefdd337 100644
--- a/src/test/java/org/tarantool/ClientReconnectClusterIT.java
+++ b/src/test/java/org/tarantool/ClientReconnectClusterIT.java
@@ -1,27 +1,43 @@
package org.tarantool;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.tarantool.AbstractTarantoolConnectorIT.makeClusterClientConfig;
+import static org.tarantool.TestUtils.makeDiscoveryFunction;
+import static org.tarantool.TestUtils.makeInstanceEnv;
+
+import org.tarantool.cluster.ClusterServiceStoredFunctionDiscovererIT;
+
import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.function.Executable;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CyclicBarrier;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.tarantool.AbstractTarantoolConnectorIT.makeClusterClientConfig;
-import static org.tarantool.TestUtils.makeInstanceEnv;
-
+@DisplayName("A cluster client")
public class ClientReconnectClusterIT {
+
+ private static final String SCHEMA_PATTERN =
+ "return box.schema.space.create('%1$s').id, box.space.%1$s:create_index('primary').id";
+
private static final int TIMEOUT = 500;
private static final String LUA_FILE = "jdk-testing.lua";
private static final String SRV1 = "replica1";
private static final String SRV2 = "replica2";
private static final String SRV3 = "replica3";
- private static final int[] PORTS = {3302, 3303, 3304};
- private static final int[] CONSOLE_PORTS = {3312, 3313, 3314};
+ private static final int[] PORTS = { 3401, 3402, 3403 };
+ private static final int[] CONSOLE_PORTS = { 3501, 3502, 3503 };
private static TarantoolControl control;
private static String REPLICATION_CONFIG = TestUtils.makeReplicationString(
@@ -29,7 +45,8 @@ public class ClientReconnectClusterIT {
AbstractTarantoolConnectorIT.password,
"localhost:" + PORTS[0],
"localhost:" + PORTS[1],
- "localhost:" + PORTS[2]);
+ "localhost:" + PORTS[2]
+ );
// Resume replication faster in case of temporary failure to fit TIMEOUT.
private static double REPLICATION_TIMEOUT = 0.1;
@@ -38,10 +55,13 @@ public class ClientReconnectClusterIT {
public static void setupEnv() {
control = new TarantoolControl();
int idx = 0;
- for (String name: Arrays.asList(SRV1, SRV2, SRV3)) {
+ for (String name : Arrays.asList(SRV1, SRV2, SRV3)) {
control.createInstance(name, LUA_FILE,
- makeInstanceEnv(PORTS[idx], CONSOLE_PORTS[idx], REPLICATION_CONFIG,
- REPLICATION_TIMEOUT));
+ makeInstanceEnv(
+ PORTS[idx], CONSOLE_PORTS[idx],
+ REPLICATION_CONFIG, REPLICATION_TIMEOUT
+ )
+ );
idx++;
}
}
@@ -57,61 +77,407 @@ public static void tearDownEnv() {
}
}
- @Test
- public void testRoundRobinReconnect() {
- control.start(SRV1);
- control.start(SRV2);
- control.start(SRV3);
+ @BeforeEach
+ public void setUpTest() {
+ startInstancesAndAwait(SRV1, SRV2, SRV3);
+ }
- control.waitStarted(SRV1);
- control.waitStarted(SRV2);
- control.waitStarted(SRV3);
+ @AfterEach
+ public void tearDownTest() {
+ stopInstancesAndAwait(SRV1, SRV2, SRV3);
+ }
- final TarantoolClientImpl client = makeClient(
+ @Test
+ @DisplayName("reconnected to another node after the current node had disappeared")
+ public void testRoundRobinReconnect() {
+ final TarantoolClientImpl client = makeClusterClient(
"localhost:" + PORTS[0],
"127.0.0.1:" + PORTS[1],
- "localhost:" + PORTS[2]);
+ "localhost:" + PORTS[2]
+ );
+
+ int[] ids = makeAndFillTestSpace(client, "rr_test1");
+ final int spaceId = ids[0];
+ final int pkId = ids[1];
+
+ expectConnected(client, spaceId, pkId);
+
+ stopInstancesAndAwait(SRV1);
+ expectConnected(client, spaceId, pkId);
+
+ stopInstancesAndAwait(SRV2);
+ expectConnected(client, spaceId, pkId);
+
+ stopInstancesAndAwait(SRV3);
+ expectDisconnected(client, spaceId, pkId);
+ }
+
+ /**
+ * Before fetch client = { srv1 }
+ * After fetch client = { srv1, srv2 }
+ *
+ * 1. fetch nodes - ok (client will apply { srv1, srv2 } as a new nodes list)
+ * 2. shutdown srv1 - ok (client will reconnect to srv2)
+ * 3. shutdown srv2 - fail (there are no available nodes anymore)
+ */
+ @Test
+ @DisplayName("applied new nodes and reconnected to another node")
+ void testUpdateExtendedNodeList() {
+ String service1Address = "localhost:" + PORTS[0];
+ String service2Address = "127.0.0.1:" + PORTS[1];
+ String service3Address = "localhost:" + PORTS[2];
+
+ CyclicBarrier barrier = new CyclicBarrier(2);
+
+ String infoFunctionName = "getAddresses";
+ String infoFunctionScript =
+ makeDiscoveryFunction(infoFunctionName, Arrays.asList(service1Address, service2Address));
+
+ control.openConsole(SRV1).exec(infoFunctionScript);
- List> ids = client.syncOps().eval(
- "return box.schema.space.create('rr_test').id, " +
- "box.space.rr_test:create_index('primary').id");
+ final TarantoolClusterClient client = makeClientWithDiscoveryFeature(
+ infoFunctionName,
+ 0,
+ (ignored) -> tryAwait(barrier),
+ service1Address
+ );
- final int spaceId = ((Number)ids.get(0)).intValue();
- final int pkId = ((Number)ids.get(1)).intValue();
+ int[] ids = makeAndFillTestSpace(client, "rr_test2");
+ final int spaceId = ids[0];
+ final int pkId = ids[1];
+
+ tryAwait(barrier); // client = { srv1 }; wait for { srv1, srv2 }
+
+ expectConnected(client, spaceId, pkId);
+
+ stopInstancesAndAwait(SRV1);
+ expectConnected(client, spaceId, pkId);
+
+ stopInstancesAndAwait(SRV2);
+ expectDisconnected(client, spaceId, pkId);
+
+ stopInstancesAndAwait(SRV3);
+ }
+
+ /**
+ * Before fetch client = { srv1, srv2 }
+ * After fetch client = { srv1 }
+ *
+ * 1. fetch nodes - ok (client will apply the narrowed { srv1 }
+ * 2. shutdown srv1 - fail (client will not reconnect to srv2 because latest is out of the list)
+ */
+ @Test
+ @DisplayName("applied new nodes and stayed connected to the current node")
+ void testUpdateNarrowNodeList() {
+ String service1Address = "localhost:" + PORTS[0];
+ String service2Address = "127.0.0.1:" + PORTS[1];
+
+ CyclicBarrier barrier = new CyclicBarrier(2);
+
+ String infoFunctionName = "getAddresses";
+ String infoFunctionScript = makeDiscoveryFunction(infoFunctionName, Collections.singletonList(service1Address));
+
+ control.openConsole(SRV1).exec(infoFunctionScript);
+
+ final TarantoolClusterClient client = makeClientWithDiscoveryFeature(
+ infoFunctionName,
+ 0,
+ (ignored) -> tryAwait(barrier),
+ service1Address,
+ service2Address
+ );
+
+ int[] ids = makeAndFillTestSpace(client, "rr_test3");
+ final int spaceId = ids[0];
+ final int pkId = ids[1];
+
+ tryAwait(barrier); // client = { srv1, srv2 }; wait for { srv1 }
+
+ expectConnected(client, spaceId, pkId);
+
+ stopInstancesAndAwait(SRV1);
+ expectDisconnected(client, spaceId, pkId);
+
+ stopInstancesAndAwait(SRV2);
+ stopInstancesAndAwait(SRV3);
+ }
+
+ /**
+ * Before fetch client = { srv1, srv2, srv3 }
+ * After fetch client = { srv1, srv2, srv3 }
+ *
+ * 1. fetch nodes - ok (client will ignore the same list)
+ * 2. shutdown srv1 - ok
+ * 3. shutdown srv2 - ok
+ * 4. shutdown srv3 - fail
+ */
+ @Test
+ @DisplayName("applied empty list and stayed connected to the current node")
+ void testUpdateEmptyNodeList() {
+ String service1Address = "localhost:" + PORTS[0];
+ String service2Address = "127.0.0.1:" + PORTS[1];
+ String service3Address = "localhost:" + PORTS[2];
+
+ String infoFunctionName = "getAddresses";
+ String infoFunctionScript = makeDiscoveryFunction(infoFunctionName, Collections.emptyList());
+
+ control.openConsole(SRV1).exec(infoFunctionScript);
+
+ final TarantoolClusterClient client = makeClientWithDiscoveryFeature(
+ infoFunctionName,
+ service1Address,
+ service2Address,
+ service3Address
+ );
+
+ int[] ids = makeAndFillTestSpace(client, "rr_test4");
+ final int spaceId = ids[0];
+ final int pkId = ids[1];
+
+ expectConnected(client, spaceId, pkId);
+
+ stopInstancesAndAwait(SRV1);
+ expectConnected(client, spaceId, pkId);
+
+ stopInstancesAndAwait(SRV2);
+ expectConnected(client, spaceId, pkId);
+
+ stopInstancesAndAwait(SRV3);
+ expectDisconnected(client, spaceId, pkId);
+ }
+
+ /**
+ * Before fetch client = { srv1, srv2, srv3 }
+ * After fetch client = { srv1, srv2, srv3 }
+ *
+ * 1. fetch with an exception (i.e. missing/error-prone function) - ok (client will ignore the failure)
+ * 2. shutdown srv1 - ok
+ * 3. shutdown srv2 - ok
+ * 4. shutdown srv3 - fail
+ *
+ * @see ClusterServiceStoredFunctionDiscovererIT#testFunctionWithError()
+ */
+ @Test
+ @DisplayName("applied nothing and stayed connected to the current node")
+ void testWrongConfigFetch() {
+ String service1Address = "localhost:" + PORTS[0];
+ String service2Address = "127.0.0.1:" + PORTS[1];
+ String service3Address = "localhost:" + PORTS[2];
+ String infoFunctionName = "getAddresses";
+ String infoFunctionScript = makeDiscoveryFunction(infoFunctionName, Collections.emptyList());
+
+ control.openConsole(SRV1).exec(infoFunctionScript);
+
+ final TarantoolClusterClient client = makeClientWithDiscoveryFeature(
+ infoFunctionName + "wrongSuffix",
+ service1Address,
+ service2Address,
+ service3Address
+ );
+
+ int[] ids = makeAndFillTestSpace(client, "rr_test5");
+ final int spaceId = ids[0];
+ final int pkId = ids[1];
+
+ expectConnected(client, spaceId, pkId);
+
+ stopInstancesAndAwait(SRV1);
+ expectConnected(client, spaceId, pkId);
+
+ stopInstancesAndAwait(SRV2);
+ expectConnected(client, spaceId, pkId);
+
+ stopInstancesAndAwait(SRV3);
+ expectDisconnected(client, spaceId, pkId);
+ }
+
+ /**
+ * Before fetch client = { srv1, srv2, srv3 }
+ * After fetch client = { srv1, srv2, srv3 }
+ *
+ * 1. fetch an improper result - ok (client will ignore the awkward data)
+ * 2. shutdown srv1 - ok
+ * 3. shutdown srv2 - ok
+ * 4. shutdown srv3 - fail
+ */
+ @Test
+ @DisplayName("ignored an wrong function result and stayed connected to the current node")
+ void testWrongFunctionResultFetch() {
+ String service1Address = "localhost:" + PORTS[0];
+ String service2Address = "127.0.0.1:" + PORTS[1];
+ String service3Address = "localhost:" + PORTS[2];
+
+ String infoFunctionName = "getWhateverExceptAddressesListFunction";
+ String infoFunctionScript = makeDiscoveryFunction(infoFunctionName, 42);
+
+ control.openConsole(SRV1).exec(infoFunctionScript);
+
+ final TarantoolClusterClient client = makeClientWithDiscoveryFeature(
+ infoFunctionName,
+ service1Address,
+ service2Address,
+ service3Address
+ );
+
+ int[] ids = makeAndFillTestSpace(client, "rr_test6");
+ final int spaceId = ids[0];
+ final int pkId = ids[1];
+
+ expectConnected(client, spaceId, pkId);
+
+ stopInstancesAndAwait(SRV1);
+ expectConnected(client, spaceId, pkId);
+
+ stopInstancesAndAwait(SRV2);
+ expectConnected(client, spaceId, pkId);
+
+ stopInstancesAndAwait(SRV3);
+ expectDisconnected(client, spaceId, pkId);
+ }
+
+ /**
+ * Before fetch client = { srv1 }
+ * After fetch ph1 client = { srv1 }
+ * After fetch ph2 client = { srv2 }
+ * After fetch ph3 client = { srv3 }
+ *
+ * 1. fetch an initial result (ph1) - ok (client will ignore the same data)
+ * 2. fetch the 2nd result (ph2) - ok (client will reconnect to srv2)
+ * 3. shutdown srv1 - ok
+ * 4. fetch the 3rd result (ph3) - ok (client will reconnect to srv3)
+ * 5. shutdown srv2 - ok
+ * 6. shutdown srv3 - fail
+ */
+ @Test
+ @DisplayName("applied each second a new cluster node and reconnected to it")
+ void testDelayFunctionResultFetch() {
+ String service1Address = "localhost:" + PORTS[0];
+ String service2Address = "127.0.0.1:" + PORTS[1];
+ String service3Address = "localhost:" + PORTS[2];
+
+ CyclicBarrier barrier = new CyclicBarrier(2);
+
+ String infoFunctionName = "getAddressesFunction";
+ String functionBody = Stream.of(service1Address, service2Address)
+ .map(address -> "coroutine.yield('" + address + "');")
+ .collect(Collectors.joining(" "));
+
+ control.openConsole(SRV1)
+ .exec("co = coroutine.create(function() " + functionBody + " end)");
+ control.openConsole(SRV1)
+ .exec("function getAddressesFunction() local c, r = coroutine.resume(co); return r end");
+
+ String infoFunctionScript = makeDiscoveryFunction(infoFunctionName, Collections.singletonList(service3Address));
+ control.openConsole(SRV2).exec(infoFunctionScript);
+
+ final TarantoolClusterClient client = makeClientWithDiscoveryFeature(
+ infoFunctionName,
+ 3000,
+ (ignored) -> tryAwait(barrier),
+ service1Address
+ );
+
+ int[] ids = makeAndFillTestSpace(client, "rr_test7");
+ final int spaceId = ids[0];
+ final int pkId = ids[1];
+
+ tryAwait(barrier); // client = { srv1 }; wait for { srv1 }
+
+ expectConnected(client, spaceId, pkId);
+
+ tryAwait(barrier); // client = { srv1 }; wait for { srv2 }
+
+ stopInstancesAndAwait(SRV1);
+ expectConnected(client, spaceId, pkId);
+
+ tryAwait(barrier); // client = { srv2 }; wait for { srv3 }
+
+ stopInstancesAndAwait(SRV2);
+ expectConnected(client, spaceId, pkId);
+
+ stopInstancesAndAwait(SRV3);
+ expectDisconnected(client, spaceId, pkId);
+ }
+
+ private void tryAwait(CyclicBarrier barrier) {
+ try {
+ barrier.await(6000, TimeUnit.MILLISECONDS);
+ } catch (Throwable e) {
+ e.printStackTrace();
+ }
+ }
+
+ private void startInstancesAndAwait(String... instances) {
+ for (String instance : instances) {
+ control.start(instance, false);
+ }
+ for (String instance : instances) {
+ control.waitStarted(instance);
+ }
+ }
+
+ private void stopInstancesAndAwait(String... instances) {
+ for (String instance : instances) {
+ control.stop(instance);
+ }
+ }
+
+ private void expectConnected(TarantoolClientImpl client, int spaceId, int pkId) {
final List> key = Collections.singletonList(1);
final List> tuple = Arrays.asList(1, 1);
- client.syncOps().insert(spaceId, tuple);
- control.waitReplication(SRV1, TIMEOUT);
-
List> res = client.syncOps().select(spaceId, pkId, key, 0, 1, Iterator.EQ);
assertEquals(res.get(0), tuple);
+ }
- control.stop(SRV1);
+ private void expectDisconnected(TarantoolClientImpl client, int spaceId, int pkId) {
+ final List> key = Collections.singletonList(1);
- res = client.syncOps().select(spaceId, pkId, key, 0, 1, Iterator.EQ);
- assertEquals(res.get(0), Arrays.asList(1, 1));
+ assertThrows(
+ CommunicationException.class,
+ () -> client.syncOps().select(spaceId, pkId, key, 0, 1, Iterator.EQ)
+ );
+ }
- control.stop(SRV2);
+ private int[] makeAndFillTestSpace(TarantoolClientImpl client, String spaceName) {
+ List> ids = client.syncOps().eval(String.format(SCHEMA_PATTERN, spaceName));
- res = client.syncOps().select(spaceId, pkId, key, 0, 1, Iterator.EQ);
- assertEquals(res.get(0), Arrays.asList(1, 1));
+ final int spaceId = ((Number) ids.get(0)).intValue();
+ final int pkId = ((Number) ids.get(1)).intValue();
- control.stop(SRV3);
+ client.syncOps().insert(spaceId, Arrays.asList(1, 1));
+ control.waitReplication(SRV1, TIMEOUT);
- CommunicationException e = assertThrows(CommunicationException.class, new Executable() {
- @Override
- public void execute() throws Throwable {
- client.syncOps().select(spaceId, pkId, key, 0, 1, Iterator.EQ);
- }
- });
+ return new int[] { spaceId, pkId };
+ }
+
+ private TarantoolClusterClient makeClusterClient(String... addresses) {
+ return makeClientWithDiscoveryFeature(null, addresses);
+ }
- assertEquals("Connection time out.", e.getMessage());
+ private TarantoolClusterClient makeClientWithDiscoveryFeature(String entryFunction,
+ String... addresses) {
+ return makeClientWithDiscoveryFeature(entryFunction, 0, null, addresses);
}
- private TarantoolClientImpl makeClient(String...addrs) {
+ private TarantoolClusterClient makeClientWithDiscoveryFeature(String entryFunction,
+ int entryDelayMillis,
+ Consumer> consumer,
+ String... addresses) {
TarantoolClusterClientConfig config = makeClusterClientConfig();
- return new TarantoolClusterClient(config, addrs);
+ config.clusterDiscoveryEntryFunction = entryFunction;
+ config.clusterDiscoveryDelayMillis = entryDelayMillis;
+
+ return new TarantoolClusterClient(config, addresses) {
+ @Override
+ protected void onInstancesRefreshed(Set instances) {
+ super.onInstancesRefreshed(instances);
+ if (consumer != null) {
+ consumer.accept(instances);
+ }
+ }
+ };
}
+
}
diff --git a/src/test/java/org/tarantool/ClientReconnectIT.java b/src/test/java/org/tarantool/ClientReconnectIT.java
index 59c4011a..b0a3e80e 100644
--- a/src/test/java/org/tarantool/ClientReconnectIT.java
+++ b/src/test/java/org/tarantool/ClientReconnectIT.java
@@ -1,5 +1,12 @@
package org.tarantool;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
+
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
@@ -18,15 +25,8 @@
import java.util.concurrent.atomic.AtomicReferenceArray;
import java.util.concurrent.locks.LockSupport;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertNull;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-import static org.junit.jupiter.api.Assertions.fail;
-
public class ClientReconnectIT extends AbstractTarantoolConnectorIT {
+
private static final String INSTANCE_NAME = "jdk-testing";
private TarantoolClient client;
@@ -100,7 +100,7 @@ public SocketChannel get(int retryNumber, Throwable lastError) {
client.syncOps().ping();
// The park() will return inside connector thread.
- LockSupport.unpark(((TarantoolClientImpl)client).connector);
+ LockSupport.unpark(((TarantoolClientImpl) client).connector);
// Wait on latch as a proof that reconnect did not happen.
// In case of a failure, latch will reach 0 before timeout occurs.
@@ -214,11 +214,54 @@ public void run() {
});
}
+ // DO NOT REMOVE THIS TEST
+ // Motivation: this test checks start/stop correctness
+ // of TarantoolControl class which is used by other tests.
+ // This test is commented out because the class is used
+ // for internal purposes only and isn't related to
+ // the connector testing.
+ // @Test
+ // @DisplayName("follow up the issue #164")
+ // void testStartStopTarantoolInstance() throws InterruptedException {
+ // int numberOfParallelInstances = 4;
+ // CountDownLatch finished = new CountDownLatch(numberOfParallelInstances);
+ // List instancesNames = new ArrayList<>(numberOfParallelInstances);
+ //
+ // for (int i = 0; i < numberOfParallelInstances; i++) {
+ // String instance = "startStop" + (i + 1);
+ // instancesNames.add(instance);
+ // control.createInstance(
+ // instancesNames.get(i),
+ // LUA_FILE,
+ // makeInstanceEnv(3401 + i + 1, 3501 + i + 1)
+ // );
+ // startTarantool(instancesNames.get(i));
+ // new Thread(() -> {
+ // for (int j = 0; j < 100; j++) {
+ // stopTarantool(instance);
+ // startTarantool(instance);
+ // if (j % 10 == 0) {
+ // System.out.println(
+ // Thread.currentThread().getName() + ": " + j + "% completed"
+ // );
+ // }
+ // }
+ // finished.countDown();
+ // }, "Thread" + (i + 1)).start();
+ // }
+ //
+ // assertTrue(finished.await(2, TimeUnit.MINUTES));
+ //
+ // for (int i = 0; i < numberOfParallelInstances; i++) {
+ // stopTarantool(instancesNames.get(i));
+ // }
+ // }
+
/**
* Test concurrent operations, reconnects and close.
- *
+ *
* Expected situation is nothing gets stuck.
- *
+ *
* The test sets SO_LINGER to 0 for outgoing connections to avoid producing
* many TIME_WAIT sockets, because an available port range can be
* exhausted.
@@ -227,13 +270,13 @@ public void run() {
public void testLongParallelCloseReconnects() {
int numThreads = 4;
int numClients = 4;
- int timeBudget = 30*1000;
+ int timeBudget = 30 * 1000;
SocketChannelProvider provider = new TestSocketChannelProvider(host,
port, RESTART_TIMEOUT).setSoLinger(0);
final AtomicReferenceArray clients =
- new AtomicReferenceArray